Project

General

Profile

Download (19.5 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
require_once("captiveportal.inc");
39

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

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

    
49
if (!$clientip) {
50
    /* not good - bail out */
51
    echo "An error occurred.  Please check the system logs for more information.";
52
    log_error("Captive portal could not determine client's IP address.");
53
    exit;
54
}
55

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

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

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

    
75
    exit;
76
}
77

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

    
83
$macfilter = !isset($config['captiveportal']['nomacfilter']);
84
$passthrumac = isset($config['captiveportal']['passthrumacadd']);
85

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

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

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

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

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

    
132
} else if ($_POST['accept'] && $_POST['auth_voucher']) {
133

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

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

    
159
} else if ($_POST['accept'] && $radius_enable) {
160

    
161
    if ($_POST['auth_user'] && $_POST['auth_pass']) {
162
        $auth_list = radius($_POST['auth_user'],$_POST['auth_pass'],$clientip,$clientmac,"USER LOGIN");
163

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

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

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

    
196
exit;
197

    
198
function portal_reply_page($redirurl, $type = null, $message = null, $clientmac = null, $clientip = null) {
199
    global $g, $config;
200

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

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

    
219
    $htmltext = str_replace("\$PORTAL_REDIRURL\$", htmlspecialchars($redirurl), $htmltext);
220
    $htmltext = str_replace("\$PORTAL_MESSAGE\$", htmlspecialchars($message), $htmltext);
221
    $htmltext = str_replace("\$CLIENT_MAC\$", htmlspecialchars($clientmac), $htmltext);
222
    $htmltext = str_replace("\$CLIENT_IP\$", htmlspecialchars($clientip), $htmltext);
223

    
224
    echo $htmltext;
225
}
226

    
227
function portal_mac_radius($clientmac,$clientip) {
228
    global $config ;
229

    
230
    $radmac_secret = $config['captiveportal']['radmac_secret'];
231

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

    
241
function portal_allow($clientip,$clientmac,$username,$password = null, $attributes = null, $ruleno = null)  {
242

    
243
    global $redirurl, $g, $config, $url_redirection, $type;
244

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

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

    
262
    // Ensure we create an array if we are missing attributes
263
    if (!is_array($attributes))
264
        $attributes = array();
265

    
266
    /* read in client database */
267
    $cpdb = captiveportal_read_db();
268

    
269
    $radiusservers = captiveportal_get_radius_servers();
270

    
271
    if ($attributes['voucher'])
272
        $remaining_time = $attributes['session_timeout'];
273

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

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

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

    
312
    $writecfg = false;
313
    if (!isset($sessionid)) {
314

    
315
        /* generate unique session ID */
316
        $tod = gettimeofday();
317
        $sessionid = substr(md5(mt_rand() . $tod['sec'] . $tod['usec'] . $clientip . $clientmac), 0, 16);
318

    
319
        /* Add rules for traffic shaping
320
         * We don't need to add extra rules since traffic will pass due to the following kernel option
321
         * net.inet.ip.fw.one_pass: 1
322
         */
323
        $peruserbw = isset($config['captiveportal']['peruserbw']);
324
	$passthrumacadd = isset($config['captiveportal']['passthrumacadd']);
325
	$portalmac = NULL;
326
	if (!empty($clientmac)) {
327
		$portalmac = portal_mac_fixed($clientmac);
328
		if ($portalmac) {
329
			$attributes['bw_up'] = $portalmac['bw_up'];
330
			$attributes['bw_down'] = $portalmac['bw_down'];
331
		}
332
	}
333

    
334
       	$bw_up = isset($attributes['bw_up']) ? trim($attributes['bw_up']) : $config['captiveportal']['bwdefaultup'];
335
       	$bw_down = isset($attributes['bw_down']) ? trim($attributes['bw_down']) : $config['captiveportal']['bwdefaultdn'];
336

    
337
	if ($passthrumacadd && $portalmac == NULL) {
338
		$mac = array();
339
		$mac['mac'] = $clientmac;
340
		$mac['descr'] =  "Auto added mac passthrough with user {$username}";
341
		if (!empty($bw_up))
342
			$mac['bw_up'] = $bw_up;
343
		if (!empty($bw_down))
344
			$mac['bw_down'] = $bw_down;
345
		if (!is_array($config['captiveportal']['passthrumac']))
346
			$config['captiveportal']['passthrumac'] = array();
347
		$config['captiveportal']['passthrumac'][] = $mac;
348
		$writecfg = true;
349
	}
350
		
351
        if ($peruserbw && !empty($bw_up) && is_numeric($bw_up)) {
352
            $bw_up_pipeno = $ruleno + 20000;
353
	    //$bw_up /= 1000; // Scale to Kbit/s
354
            mwexec("/sbin/ipfw pipe {$bw_up_pipeno} config bw {$bw_up}Kbit/s queue 100");
355

    
356
	    if (!isset($config['captiveportal']['nomacfilter']) || $passthrumacadd)
357
		mwexec("/sbin/ipfw table 1 add {$clientip} mac {$clientmac} {$bw_up_pipeno}");
358
	    else
359
	    	mwexec("/sbin/ipfw table 1 add {$clientip} {$bw_up_pipeno}");
360
        } else {
361
	    if (!isset($config['captiveportal']['nomacfilter']) || $passthrumacadd)
362
		mwexec("/sbin/ipfw table 1 add {$clientip} mac {$clientmac}");
363
	    else
364
            	mwexec("/sbin/ipfw table 1 add {$clientip}");
365
        }
366
        if ($peruserbw && !empty($bw_down) && is_numeric($bw_down)) {
367
            $bw_down_pipeno = $ruleno + 20001;
368
	    //$bw_down /= 1000; // Scale to Kbit/s
369
	    mwexec("/sbin/ipfw pipe {$bw_down_pipeno} config bw {$bw_down}Kbit/s queue 100");
370

    
371
	    if (!isset($config['captiveportal']['nomacfilter']) || $passthrumacadd)
372
                mwexec("/sbin/ipfw table 2 add {$clientip} mac {$clientmac} {$bw_down_pipeno}");
373
            else
374
                mwexec("/sbin/ipfw table 2 add {$clientip} {$bw_down_pipeno}");
375
        } else {
376
            if (!isset($config['captiveportal']['nomacfilter']) || $passthrumacadd)
377
                mwexec("/sbin/ipfw table 2 add {$clientip} mac {$clientmac}");
378
            else
379
                mwexec("/sbin/ipfw table 2 add {$clientip}");
380
        }
381

    
382
	if ($attributes['voucher'])
383
		$attributes['session_timeout'] = $remaining_time;
384

    
385
        /* encode password in Base64 just in case it contains commas */
386
        $bpassword = base64_encode($password);
387
        $cpdb[] = array(time(), $ruleno, $clientip, $clientmac, $username, $sessionid, $bpassword,
388
                $attributes['session_timeout'],
389
                $attributes['idle_timeout'],
390
                $attributes['session_terminate_time']);
391

    
392
        if (isset($config['captiveportal']['radacct_enable']) && !empty($radiusservers)) {
393
            $acct_val = RADIUS_ACCOUNTING_START($ruleno,
394
                                                            $username,
395
                                                            $sessionid,
396
                                                            $radiusservers,
397
                                                            $clientip,
398
                                                            $clientmac);
399

    
400
            if ($acct_val == 1)
401
                captiveportal_logportalauth($username,$clientmac,$clientip,$type,"RADIUS ACCOUNTING FAILED");
402
        }
403

    
404
    	/* rewrite information to database */
405
    	captiveportal_write_db($cpdb);
406
    }
407

    
408
    if ($captiveshouldunlock == true)
409
	unlock($cplock);
410

    
411
    if ($writecfg == true) {
412
	write_config();
413
	captiveportal_passthrumac_configure(true);
414
    }
415

    
416
    /* redirect user to desired destination */
417
    if ($url_redirection)
418
        $my_redirurl = $url_redirection;
419
    else if ($config['captiveportal']['redirurl'])
420
        $my_redirurl = $config['captiveportal']['redirurl'];
421
    else
422
        $my_redirurl = $redirurl;
423

    
424
    if(isset($config['captiveportal']['logoutwin_enable']) && !isset($config['captiveportal']['passthrumacadd'])) {
425

    
426
        if (isset($config['captiveportal']['httpslogin']))
427
            $logouturl = "https://{$config['captiveportal']['httpsname']}:8001/";
428
        else {
429
	    $ifip = portal_ip_from_client_ip($clientip);
430
    	    if (!$ifip)
431
        	$ourhostname = $config['system']['hostname'] . ":8000";
432
            else
433
        	$ourhostname = "{$ifip}:8000";
434
            $logouturl = "http://{$ourhostname}/";
435
	}
436

    
437
        echo <<<EOD
438
<HTML>
439
<HEAD><TITLE>Redirecting...</TITLE></HEAD>
440
<BODY>
441
<SPAN STYLE="font-family: Tahoma, Verdana, Arial, Helvetica, sans-serif; font-size: 11px;">
442
<B>Redirecting to <A HREF="{$my_redirurl}">{$my_redirurl}</A>...</B>
443
</SPAN>
444
<SCRIPT LANGUAGE="JavaScript">
445
<!--
446
LogoutWin = window.open('', 'Logout', 'toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=0,width=256,height=64');
447
if (LogoutWin) {
448
    LogoutWin.document.write('<HTML>');
449
    LogoutWin.document.write('<HEAD><TITLE>Logout</TITLE></HEAD>') ;
450
    LogoutWin.document.write('<BODY BGCOLOR="#435370">');
451
    LogoutWin.document.write('<DIV ALIGN="center" STYLE="color: #ffffff; font-family: Tahoma, Verdana, Arial, Helvetica, sans-serif; font-size: 11px;">') ;
452
    LogoutWin.document.write('<B>Click the button below to disconnect</B><P>');
453
    LogoutWin.document.write('<FORM METHOD="POST" ACTION="{$logouturl}">');
454
    LogoutWin.document.write('<INPUT NAME="logout_id" TYPE="hidden" VALUE="{$sessionid}">');
455
    LogoutWin.document.write('<INPUT NAME="logout" TYPE="submit" VALUE="Logout">');
456
    LogoutWin.document.write('</FORM>');
457
    LogoutWin.document.write('</DIV></BODY>');
458
    LogoutWin.document.write('</HTML>');
459
    LogoutWin.document.close();
460
}
461

    
462
document.location.href="{$my_redirurl}";
463
-->
464
</SCRIPT>
465
</BODY>
466
</HTML>
467

    
468
EOD;
469
    } else {
470
        header("Location: " . $my_redirurl);
471
		return $sessionid;
472
    }
473

    
474
    return $sessionid;
475
}
476

    
477

    
478

    
479
/* remove a single client by session ID
480
   by Dinesh Nair
481
 */
482
function disconnect_client($sessionid, $logoutReason = "LOGOUT", $term_cause = 1) {
483

    
484
    global $g, $config;
485

    
486
    $cplock = lock('captiveportal');
487
    /* read database */
488
    $cpdb = captiveportal_read_db();
489

    
490
    $radiusservers = captiveportal_get_radius_servers();
491

    
492
    /* find entry */
493
    $dbcount = count($cpdb);
494
    for ($i = 0; $i < $dbcount; $i++) {
495
        if ($cpdb[$i][5] == $sessionid) {
496
            captiveportal_disconnect($cpdb[$i],$radiusservers, $term_cause);
497
            captiveportal_logportalauth($cpdb[$i][4],$cpdb[$i][3],$cpdb[$i][2],$logoutReason);
498
            unset($cpdb[$i]);
499
            break;
500
        }
501
    }
502

    
503
    /* write database */
504
    captiveportal_write_db($cpdb);
505

    
506
    unlock($cplock);
507
}
508

    
509
?>
(1-1/3)