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
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

    
85
/* find MAC address for client */
86
$clientmac = arp_get_mac_by_ip($clientip);
87
if (!$clientmac && $macfilter) {
88
    /* unable to find MAC address - shouldn't happen! - bail out */
89
    captiveportal_logportalauth("unauthenticated","noclientmac",$clientip,"ERROR");
90
    echo "An error occurred.  Please check the system logs for more information.";
91
    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.");
92
    exit;
93
}
94

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

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

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

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

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

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

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

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

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

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

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

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

    
194
exit;
195

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

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

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

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

    
222
    echo $htmltext;
223
}
224

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

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

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

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

    
241
    global $redirurl, $g, $config, $url_redirection, $type;
242

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

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

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

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

    
267
    $radiusservers = captiveportal_get_radius_servers();
268

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
440
    return $sessionid;
441
}
442

    
443

    
444

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

    
450
    global $g, $config;
451

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

    
456
    $radiusservers = captiveportal_get_radius_servers();
457

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

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

    
471
    unlock($cplock);
472
}
473

    
474
?>
(1-1/3)