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 ($cpdb[$i][4] == $username) {
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

    
375
    }
376

    
377
    /* rewrite information to database */
378
    captiveportal_write_db($cpdb);
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)