Project

General

Profile

Bug #14118 ยป 23_01-Mar-14-2023-captiveportal-MODIFIED.inc

Dale Harron, 03/16/2023 10:11 AM

 
1
<?php
2
/*
3
 * captiveportal.inc
4
 *
5
 * part of pfSense (https://www.pfsense.org)
6
 * Copyright (c) 2004-2013 BSD Perimeter
7
 * Copyright (c) 2013-2016 Electric Sheep Fencing
8
 * Copyright (c) 2014-2021 Rubicon Communications, LLC (Netgate)
9
 * All rights reserved.
10
 *
11
 * originally part of m0n0wall (http://m0n0.ch/wall)
12
 * Copyright (c) 2003-2006 Manuel Kasper <mk@neon1.net>.
13
 * All rights reserved.
14
 *
15
 * Licensed under the Apache License, Version 2.0 (the "License");
16
 * you may not use this file except in compliance with the License.
17
 * You may obtain a copy of the License at
18
 *
19
 * http://www.apache.org/licenses/LICENSE-2.0
20
 *
21
 * Unless required by applicable law or agreed to in writing, software
22
 * distributed under the License is distributed on an "AS IS" BASIS,
23
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
24
 * See the License for the specific language governing permissions and
25
 * limitations under the License.
26
 */
27

    
28
/* include all configuration functions */
29
require_once("auth.inc");
30
require_once("PEAR.php"); // required for bcmath
31
require_once("Auth/RADIUS.php"); // required for radius accounting
32
require_once("config.inc");
33
require_once("functions.inc");
34
require_once("filter.inc");
35
require_once("voucher.inc");
36
require_once("xmlrpc_client.inc");
37

    
38
/* Captiveportal Radius Accounting */
39
PEAR::loadExtension('bcmath');
40
// The RADIUS Package doesn't have these vars so we create them ourself
41
define("CUSTOM_RADIUS_ACCT_INPUT_GIGAWORDS", "52");
42
define("CUSTOM_RADIUS_ACCT_OUTPUT_GIGAWORDS", "53");
43
define("GIGAWORDS_RIGHT_OPERAND", '4294967296'); // 2^32
44

    
45
function get_captive_portal_logo() {
46
	global $config, $g, $cpzone;
47
	$logo_src = "captiveportal-default-logo.png";
48
	// Check if customlogo is set and if the element exists
49
	// Check if the image is in the directory
50
	if (isset($config['captiveportal'][$cpzone]['customlogo']) &&
51
	    is_array($config['captiveportal'][$cpzone]['element']) &&
52
	    !empty($config['captiveportal'][$cpzone]['element'])) {
53
		foreach ($config['captiveportal'][$cpzone]['element'] as $element) {
54
			if (strpos($element['name'], "captiveportal-logo.") !== false) {
55
				if (file_exists("{$g['captiveportal_path']}/{$element['name']}")) {
56
					$logo_src = $element['name'];
57
					break;
58
				}
59
			}
60
		}
61
	}
62
	return $logo_src;
63
}
64

    
65
function get_captive_portal_bg() {
66
	$bg_src = "linear-gradient(135deg, #1475CF, #2B40B5, #1C1275)";
67
	global $config, $g, $cpzone;
68
	// check if custombg is set and if the element exists
69
	if (isset($config['captiveportal'][$cpzone]['custombg']) &&
70
	    is_array($config['captiveportal'][$cpzone]['element']) &&
71
	    !empty($config['captiveportal'][$cpzone]['element'])) { 
72
		foreach ($config['captiveportal'][$cpzone]['element'] as $element) {
73
			if (strpos($element['name'],"captiveportal-background.") !== false) {
74
				if( file_exists("{$g['captiveportal_path']}/{$element['name']}")) {
75
					$bg_src = "url(" . $element['name'] . ")" . "center center no-repeat fixed";
76
					break;
77
				}
78
			}
79
		}
80
	}
81
	return $bg_src;
82
}
83

    
84
function get_default_captive_portal_html() {
85
	global $config, $g, $cpzone;
86

    
87
	$translated_text1 = gettext("User");
88
	$translated_text2 = gettext("Password");
89
	$translated_text3 = gettext("First Authentication Method ");
90
	$translated_text4 = gettext("Second Authentication Method ");
91
	// default images to use.
92
	$logo_src = get_captive_portal_logo();
93
	$bg_src = get_captive_portal_bg();
94
	// bring in terms and conditions
95
	$termsconditions = base64_decode($config['captiveportal'][$cpzone]['termsconditions']);
96
	// if there is no terms and conditions do not require the checkbox to be selected.
97
	$disabled = "";
98
	if ($termsconditions) {
99
		$disabled = "disabled";
100
	}
101
	$htmltext = <<<EOD
102
<!DOCTYPE html>
103
<html>
104

    
105
<head>
106

    
107
  <meta charset="UTF-8">
108
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
109
  <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
110
  <title>Captive Portal Login Page</title>
111
  <style>
112
	  #content,.login,.login-card a,.login-card h1,.login-help{text-align:center}body,html{margin:0;padding:0;width:100%;height:100%;display:table}#content{font-family:'Source Sans Pro',sans-serif;background-color:#1C1275;background:{$bg_src};-webkit-background-size:cover;-moz-background-size:cover;-o-background-size:cover;background-size:cover;display:table-cell;vertical-align:middle}.login-card{padding:40px;width:280px;background-color:#F7F7F7;margin:100px auto 10px;border-radius:2px;box-shadow:0 2px 2px rgba(0,0,0,.3);overflow:hidden}.login-card h1{font-weight:400;font-size:2.3em;color:#1383c6}.login-card h1 span{color:#f26721}.login-card img{width:70%;height:70%}.login-card input[type=submit]{width:100%;display:block;margin-bottom:10px;position:relative}.login-card input[type=text],input[type=password]{height:44px;font-size:16px;width:100%;margin-bottom:10px;-webkit-appearance:none;background:#fff;border:1px solid #d9d9d9;border-top:1px solid silver;padding:0 8px;box-sizing:border-box;-moz-box-sizing:border-box}.login-card input[type=text]:hover,input[type=password]:hover{border:1px solid #b9b9b9;border-top:1px solid #a0a0a0;-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.login{font-size:14px;font-family:Arial,sans-serif;font-weight:700;height:36px;padding:0 8px}.login-submit{-webkit-appearance:none;-moz-appearance:none;appearance:none;border:0;color:#fff;text-shadow:0 1px rgba(0,0,0,.1);background-color:#4d90fe}.login-submit:disabled{opacity:.6}.login-submit:hover{border:0;text-shadow:0 1px rgba(0,0,0,.3);background-color:#357ae8}.login-card a{text-decoration:none;color:#222;font-weight:400;display:inline-block;opacity:.6;transition:opacity ease .5s}.login-card a:hover{opacity:1}.login-help{width:100%;font-size:12px}.list{list-style-type:none;padding:0}.list__item{margin:0 0 .7rem;padding:0}label{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;text-align:left;font-size:14px;}input[type=checkbox]{-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none;margin-right:10px;float:left}@media screen and (max-width:450px){.login-card{width:70%!important}.login-card img{width:30%;height:30%}}textarea{width:66%;margin:auto;height:120px;max-height:120px;background-color:#f7f7f7;padding:20px}#terms{display:none;padding-top:100px;padding-bottom:300px;}.auth_source{border: 1px solid lightgray; padding:20px 8px 0px 8px; margin-top: -2em; border-radius: 2px; }.auth_head{background-color:#f7f7f7;display:inline-block;}.auth_head_div{text-align:left;}#error-message{text-align:left;color:#ff3e3e;font-style:italic;}
113
  </style>
114
</head>
115

    
116
<body>
117
<div id="content">
118
	<div class="login-card">
119
		<img src="{$logo_src}"/><br>
120
 		<h1></h1>
121
		<div id="error-message">
122
			\$PORTAL_MESSAGE\$
123
		</div>
124
	  <form name="login_form" method="post" action="\$PORTAL_ACTION\$">
125
EOD;
126
	if ($config['captiveportal'][$cpzone]['auth_method'] != "none"){
127
		if ($config['captiveportal'][$cpzone]['auth_method'] === 'authserver' && !empty($config['captiveportal'][$cpzone]['auth_server2'])) {
128
			$htmltext .= <<<EOD
129
			<div class="auth_head_div">
130
				<h6 class="auth_head">{$translated_text3}</h6>
131
			</div>
132
			<div class="auth_source">
133

    
134
EOD;
135
		}
136
		$htmltext .=<<<EOD
137
		<input type="text" name="auth_user" placeholder="{$translated_text1}" id="auth_user">
138
		<input type="password" name="auth_pass" placeholder="{$translated_text2}" id="auth_pass">
139
EOD;
140

    
141
		if ($config['captiveportal'][$cpzone]['auth_method'] === 'authserver' && !empty($config['captiveportal'][$cpzone]['auth_server2'])) {
142
			$htmltext .= <<<EOD
143
			</div>
144
			<div class="auth_head_div">
145
				<h6 class="auth_head">{$translated_text4}</h6>
146
			</div>
147
			<div class="auth_source">
148

    
149
			<input type="text" name="auth_user2" placeholder="{$translated_text1}" id="auth_user2">
150
			<input type="password" name="auth_pass2" placeholder="{$translated_text2}" id="auth_pass2">
151
			</div>
152
EOD;
153
		}
154

    
155

    
156
		if (isset($config['voucher'][$cpzone]['enable'])) {
157
			$translated_text = gettext("Voucher Code");
158
			$htmltext .= <<<EOD
159
				<br  /><br  />
160
				<input name="auth_voucher" type="text" placeholder="{$translated_text}" value="#VOUCHER#">
161
EOD;
162
		}
163
	}
164

    
165
if ($termsconditions) {
166
	$htmltext .= <<<EOD
167
		  <div class="login-help">
168
			<ul class="list">
169
				<li class="list__item">
170
				  <label class="label--checkbox">
171
					<input type="checkbox" class="checkbox" onchange="document.getElementById('login').disabled = !this.checked;">
172
					<span>I agree with the <a  rel="noopener" href="#terms" onclick="document.getElementById('terms').style.display = 'block';">terms & conditions</a></span>
173
				  </label>
174
				</li>
175
			</ul>
176
		  </div>
177
EOD;
178
}
179
	$htmltext .= <<<EOD
180

    
181
		<input name="redirurl" type="hidden" value="\$PORTAL_REDIRURL\$">
182
		<input type="submit" name="accept" class="login login-submit" value="Login" id="login" {$disabled}>
183
	  </form>
184
	  <br  />
185
	  <span> <i>Made with &hearts; by</i> <strong>Netgate</strong></span>
186
	</div>
187
	<div id="terms">
188
		<textarea readonly>{$termsconditions}</textarea>
189
	</div>
190
</div>
191
</body>
192
</html>
193

    
194
EOD;
195

    
196
	return $htmltext;
197
}
198

    
199
function captiveportal_configure() {
200
	global $config, $g, $cpzone, $cpzoneid;
201

    
202
	if (is_array($config['captiveportal'])) {
203
		foreach ($config['captiveportal'] as $cpzone) {
204
			if (isset($cpzone['preservedb'])) {
205
				$keep_online_users = true;
206
				break;
207
			}
208
		}
209
		if (!$keep_online_users) {
210
			/* see https://redmine.pfsense.org/issues/12455 */
211
			unlink_if_exists("{$g['vardb_path']}/captiveportal_online_users");
212
		}
213
		foreach ($config['captiveportal'] as $cpkey => $cp) {
214
			$cpzone = $cpkey;
215
			$cpzoneid = $cp['zoneid'];
216
			captiveportal_configure_zone($cp);
217
		}
218
	}
219
}
220

    
221
function captiveportal_configure_zone($cpcfg, $reload_rules = false) {
222
	global $config, $g, $cpzone, $cpzoneid;
223

    
224
	$captiveportallck = lock("captiveportal{$cpzone}", LOCK_EX);
225

    
226
	if (isset($cpcfg['enable'])) {
227

    
228
		if (platform_booting()) {
229
			echo "Starting captive portal({$cpcfg['zone']})... ";
230
		} else {
231
			captiveportal_syslog("Reconfiguring captive portal({$cpcfg['zone']}).");
232
		}
233

    
234
		/* init captive portal pipes and anchors */
235
		captiveportal_init_rules($reload_rules);
236

    
237
		/* kill any running minicron */
238
		killbypid("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid");
239

    
240
		/* initialize minicron interval value */
241
		$croninterval = $cpcfg['croninterval'] ? $cpcfg['croninterval'] : 60;
242

    
243
		/* double check if the $croninterval is numeric and at least 10 seconds. If not we set it to 60 to avoid problems */
244
		if ((!is_numeric($croninterval)) || ($croninterval < 10)) {
245
			$croninterval = 60;
246
		}
247

    
248
		/* write portal page */
249
		if (is_array($cpcfg['page']) && $cpcfg['page']['htmltext']) {
250
			$htmltext = base64_decode($cpcfg['page']['htmltext']);
251
		} else {
252
			/* example/template page */
253
			$htmltext = get_default_captive_portal_html();
254
		}
255

    
256
		$fd = @fopen("{$g['varetc_path']}/captiveportal_{$cpzone}.html", "w");
257
		if ($fd) {
258
			// Special case handling.  Convert so that we can pass this page
259
			// through the PHP interpreter later without clobbering the vars.
260
			$htmltext = str_replace("\$PORTAL_ZONE\$", "#PORTAL_ZONE#", $htmltext);
261
			$htmltext = str_replace("\$PORTAL_REDIRURL\$", "#PORTAL_REDIRURL#", $htmltext);
262
			$htmltext = str_replace("\$PORTAL_MESSAGE\$", "#PORTAL_MESSAGE#", $htmltext);
263
			$htmltext = str_replace("\$CLIENT_MAC\$", "#CLIENT_MAC#", $htmltext);
264
			$htmltext = str_replace("\$CLIENT_IP\$", "#CLIENT_IP#", $htmltext);
265
			$htmltext = str_replace("\$PORTAL_ACTION\$", "#PORTAL_ACTION#", $htmltext);
266
			if ($cpcfg['preauthurl']) {
267
				$htmltext = str_replace("\$PORTAL_REDIRURL\$", "{$cpcfg['preauthurl']}", $htmltext);
268
				$htmltext = str_replace("#PORTAL_REDIRURL#", "{$cpcfg['preauthurl']}", $htmltext);
269
			}
270
			fwrite($fd, $htmltext);
271
			fclose($fd);
272
		}
273
		unset($htmltext);
274

    
275
		/* write error page */
276
		if (is_array($cpcfg['page']) && $cpcfg['page']['errtext']) {
277
			$errtext = base64_decode($cpcfg['page']['errtext']);
278
		} else {
279
			/* example page  */
280
			$errtext = get_default_captive_portal_html();
281
		}
282

    
283
		$fd = @fopen("{$g['varetc_path']}/captiveportal-{$cpzone}-error.html", "w");
284
		if ($fd) {
285
			// Special case handling.  Convert so that we can pass this page
286
			// through the PHP interpreter later without clobbering the vars.
287
			$errtext = str_replace("\$PORTAL_ZONE\$", "#PORTAL_ZONE#", $errtext);
288
			$errtext = str_replace("\$PORTAL_REDIRURL\$", "#PORTAL_REDIRURL#", $errtext);
289
			$errtext = str_replace("\$PORTAL_MESSAGE\$", "#PORTAL_MESSAGE#", $errtext);
290
			$errtext = str_replace("\$CLIENT_MAC\$", "#CLIENT_MAC#", $errtext);
291
			$errtext = str_replace("\$CLIENT_IP\$", "#CLIENT_IP#", $errtext);
292
			$errtext = str_replace("\$PORTAL_ACTION\$", "#PORTAL_ACTION#", $errtext);
293
			if ($cpcfg['preauthurl']) {
294
				$errtext = str_replace("\$PORTAL_REDIRURL\$", "{$cpcfg['preauthurl']}", $errtext);
295
				$errtext = str_replace("#PORTAL_REDIRURL#", "{$cpcfg['preauthurl']}", $errtext);
296
			}
297
			fwrite($fd, $errtext);
298
			fclose($fd);
299
		}
300
		unset($errtext);
301

    
302
		/* write logout page */
303
		if (is_array($cpcfg['page']) && $cpcfg['page']['logouttext']) {
304
			$logouttext = base64_decode($cpcfg['page']['logouttext']);
305
		} else {
306
			/* example page */
307
			$translated_text1 = gettext("Redirecting...");
308
			$translated_text2 = gettext("Redirecting to");
309
			$translated_text3 = gettext("Logout");
310
			$translated_text4 = gettext("Click the button below to disconnect");
311
			$logouttext = <<<EOD
312
<html>
313
<head><title>{$translated_text1}</title></head>
314
<body>
315
<span style="font-family: Tahoma, Verdana, Arial, Helvetica, sans-serif; font-size: 11px;">
316
<b>{$translated_text2} <a href="<?=\$my_redirurl;?>"><?=\$my_redirurl;?></a>...</b>
317
</span>
318
<script type="text/javascript">
319
//<![CDATA[
320
LogoutWin = window.open('', 'Logout', 'toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=0,width=256,height=64');
321
if (LogoutWin) {
322
	LogoutWin.document.write('<html>');
323
	LogoutWin.document.write('<head><title>{$translated_text3}</title></head>') ;
324
	LogoutWin.document.write('<body style="background-color:#435370">');
325
	LogoutWin.document.write('<div class="text-center" style="color: #ffffff; font-family: Tahoma, Verdana, Arial, Helvetica, sans-serif; font-size: 11px;">') ;
326
	LogoutWin.document.write('<b>{$translated_text4}</b><p />');
327
	LogoutWin.document.write('<form method="POST" action="<?=\$logouturl;?>">');
328
	LogoutWin.document.write('<input name="logout_id" type="hidden" value="<?=\$sessionid;?>" />');
329
	LogoutWin.document.write('<input name="zone" type="hidden" value="<?=\$cpzone;?>" />');
330
	LogoutWin.document.write('<input name="logout" type="submit" value="{$translated_text3}" />');
331
	LogoutWin.document.write('</form>');
332
	LogoutWin.document.write('</div></body>');
333
	LogoutWin.document.write('</html>');
334
	LogoutWin.document.close();
335
}
336

    
337
document.location.href="<?=\$my_redirurl;?>";
338
//]]>
339
</script>
340
</body>
341
</html>
342

    
343
EOD;
344
		}
345

    
346
		$fd = @fopen("{$g['varetc_path']}/captiveportal-{$cpzone}-logout.html", "w");
347
		if ($fd) {
348
			fwrite($fd, $logouttext);
349
			fclose($fd);
350
		}
351
		unset($logouttext);
352

    
353
		/* write elements */
354
		captiveportal_write_elements();
355

    
356
		/* kill any running CP nginx instances */
357
		killbypid("{$g['varrun_path']}/nginx-{$cpzone}-CaptivePortal.pid", 0.1);
358
		killbypid("{$g['varrun_path']}/nginx-{$cpzone}-CaptivePortal-SSL.pid", 0.1);
359

    
360
		/* start up the webserving daemon */
361
		captiveportal_init_webgui_zone($cpcfg);
362

    
363
		/* Kill any existing prunecaptiveportal processes */
364
		if (file_exists("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid")) {
365
			killbypid("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid");
366
		}
367

    
368
		/* start pruning process (interval defaults to 60 seconds) */
369
		mwexec("/usr/local/bin/minicron $croninterval {$g['varrun_path']}/cp_prunedb_{$cpzone}.pid " .
370
			"/etc/rc.prunecaptiveportal {$cpzone}");
371

    
372
		/* delete outdated radius server database if exist */
373
		unlink_if_exists("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db");
374

    
375
		if (platform_booting() || $reload_rules) {
376
			/* send Accounting-On to server */
377
			captiveportal_send_server_accounting('on');
378
			echo "done\n";
379

    
380
			if (isset($cpcfg['preservedb']) || $reload_rules ||
381
			    captiveportal_xmlrpc_sync_get_details($syncip, $syncport, $syncuser, $syncpass)) {
382

    
383
				$connected_users = captiveportal_read_db();
384
				if (!empty($connected_users)) {
385
					echo "Reconnecting users to captive portal {$cpcfg['zone']}... ";
386
					foreach ($connected_users as $user) {
387
						captiveportal_reserve_ruleno($user['pipeno']);
388
						captiveportal_ether_configure_entry($user, 'auth', true);
389
					}
390
					echo "done\n";
391
				}
392
			} else {
393
				/* reset database on unclean shutdown, see https://redmine.pfsense.org/issues/12355 */
394
				unlink_if_exists("{$g['vardb_path']}/captiveportal{$cpzone}.db");
395
			}
396
		}
397
	} else {
398
		killbypid("{$g['varrun_path']}/nginx-{$cpzone}-CaptivePortal.pid");
399
		killbypid("{$g['varrun_path']}/nginx-{$cpzone}-CaptivePortal-SSL.pid");
400
		killbypid("{$g['varrun_path']}/cp_prunedb_{$cpzone}.pid");
401
		@unlink("{$g['varetc_path']}/captiveportal_{$cpzone}.html");
402
		@unlink("{$g['varetc_path']}/captiveportal-{$cpzone}-error.html");
403
		@unlink("{$g['varetc_path']}/captiveportal-{$cpzone}-logout.html");
404

    
405
		captiveportal_radius_stop_all(10); // NAS-Request
406

    
407
		/* Release allocated pipes for this zone */
408
		$pipes_to_remove = captiveportal_free_dnrules();
409
		captiveportal_delete_rules($pipes_to_remove);
410

    
411
		/* remove old information */
412
		unlink_if_exists("{$g['vardb_path']}/captiveportal{$cpzone}.db");
413
		unlink_if_exists("{$g['vardb_path']}/captiveportal_radius_{$cpzone}.db");
414
		unlink_if_exists("{$g['vardb_path']}/captiveportal_{$cpzone}.rules");
415
	}
416

    
417
	unlock($captiveportallck);
418

    
419
	return 0;
420
}
421

    
422
function captiveportal_init_webgui() {
423
	global $config, $cpzone;
424

    
425
	if (is_array($config['captiveportal'])) {
426
		foreach ($config['captiveportal'] as $cpkey => $cp) {
427
			$cpzone = $cpkey;
428
			captiveportal_init_webgui_zone($cp);
429
		}
430
	}
431
}
432

    
433
function captiveportal_init_webgui_zonename($zone) {
434
	global $config, $cpzone;
435

    
436
	if (isset($config['captiveportal'][$zone])) {
437
		$cpzone = $zone;
438
		captiveportal_init_webgui_zone($config['captiveportal'][$zone]);
439
	}
440
}
441

    
442
function captiveportal_init_webgui_zone($cpcfg) {
443
	global $g, $config, $cpzone;
444

    
445
	if (!isset($cpcfg['enable'])) {
446
		return;
447
	}
448

    
449
	if (isset($cpcfg['httpslogin'])) {
450
		$cert = lookup_cert($cpcfg['certref']);
451
		$crt = base64_decode($cert['crt']);
452
		$key = base64_decode($cert['prv']);
453
		$ca = ca_chain($cert);
454

    
455
		/* generate nginx configuration */
456
		if (!empty($cpcfg['listenporthttps'])) {
457
			$listenporthttps = $cpcfg['listenporthttps'];
458
		} else {
459
			$listenporthttps = 8001 + $cpcfg['zoneid'];
460
		}
461
		system_generate_nginx_config("{$g['varetc_path']}/nginx-{$cpzone}-CaptivePortal-SSL.conf",
462
			$crt, $key, $ca, "nginx-{$cpzone}-CaptivePortal-SSL.pid", $listenporthttps, "/usr/local/captiveportal",
463
			"cert-{$cpzone}-portal.pem", "ca-{$cpzone}-portal.pem", $cpzone, false);
464
	}
465

    
466
	/* generate nginx configuration */
467
	if (!empty($cpcfg['listenporthttp'])) {
468
		$listenporthttp = $cpcfg['listenporthttp'];
469
	} else {
470
		$listenporthttp = 8000 + $cpcfg['zoneid'];
471
	}
472
	system_generate_nginx_config("{$g['varetc_path']}/nginx-{$cpzone}-CaptivePortal.conf",
473
		"", "", "", "nginx-{$cpzone}-CaptivePortal.pid", $listenporthttp, "/usr/local/captiveportal",
474
		"", "", $cpzone, false);
475

    
476
	@unlink("{$g['varrun']}/nginx-{$cpzone}-CaptivePortal.pid");
477
	/* attempt to start nginx */
478
	$res = mwexec("/usr/local/sbin/nginx -c {$g['varetc_path']}/nginx-{$cpzone}-CaptivePortal.conf");
479

    
480
	/* fire up https instance */
481
	if (isset($cpcfg['httpslogin'])) {
482
		@unlink("{$g['varrun']}/nginx-{$cpzone}-CaptivePortal-SSL.pid");
483
		$res = mwexec("/usr/local/sbin/nginx -c {$g['varetc_path']}/nginx-{$cpzone}-CaptivePortal-SSL.conf");
484
	}
485
}
486

    
487
/* reinit will disconnect all users, be careful! */
488
function captiveportal_init_rules($reinit = false) {
489
	global $config, $g, $cpzone;
490

    
491
	if (!isset($config['captiveportal'][$cpzone]['enable'])) {
492
		return;
493
	}
494

    
495
	dummynet_load_module('100');
496

    
497
	/* Cleanup so nothing is leaked */
498
	captiveportal_free_dnrules(2000, 64500, false, $reinit);
499

    
500
	$captiveportallck = try_lock("captiveportal{$cpzone}", 0);
501

    
502
	/* delete all anchors */
503
	captiveportal_delete_rules(array(), $reinit);
504

    
505
	/* load passthru mac anchors */
506
	captiveportal_passthrumac_configure();
507

    
508
	/* load allowedip anchors */
509
	captiveportal_allowedip_configure();
510

    
511
	/* load allowed hostname anchors */
512
	captiveportal_allowedhostname_configure();
513

    
514
	if ($captiveportallck) {
515
		unlock($captiveportallck);
516
	}
517
}
518

    
519
/* Delete all rules related to specific cpzone */
520
function captiveportal_delete_rules($pipes_to_remove = array(), $clear_auth_rules = true) {
521
	global $g, $config, $cpzone;
522

    
523
	/* delete MAC passthru entries */
524
	init_config_arr(array('captiveportal', $cpzone, 'passthrumac'));
525
	foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $macent) {
526
		captiveportal_passthrumac_delete_entry($macent);
527
	}
528

    
529
	/* delete Allowed IP entries */
530
	init_config_arr(array('captiveportal', $cpzone, 'allowedip'));
531
	if (is_array($config['captiveportal'][$cpzone]['allowedip'])) {
532
		foreach ($config['captiveportal'][$cpzone]['allowedip'] as $ipent) {
533
			captiveportal_ether_delete_entry($ipent, 'allowedhosts');
534
		}
535
	}
536

    
537
	/* delete Allowed Hostnames entries */
538
	captiveportal_allowedhostname_cleanup();
539

    
540
	/* delete authenticated clients rules */
541
	$connected_users = captiveportal_read_db();
542
	if (!empty($connected_users) && $clear_auth_rules) {
543
		foreach ($connected_users as $user) {
544
			captiveportal_ether_delete_entry($user, 'auth');
545
		}
546
	}
547

    
548
	/* delete pipes */
549
	captiveportal_pipes_delete($pipes_to_remove);
550
}
551

    
552
/*
553
 * Remove clients that have been around for longer than the specified amount of time
554
 * db file structure:
555
 * timestamp,ipfw_rule_no(deprecated),clientip,clientmac,username,sessionid,password,session_timeout,idle_timeout,session_terminate_time,interim_interval,traffic_quota,auth_method,context
556
 * (password is in Base64 and only saved when reauthentication is enabled)
557
 */
558
function captiveportal_prune_old() {
559
	global $g, $config, $cpzone, $cpzoneid;
560

    
561
	if (empty($cpzone)) {
562
		return;
563
	}
564

    
565
	$cpcfg = config_get_path("captiveportal/{$cpzone}");
566
	$vcpcfg = config_get_path("voucher/{$cpzone}");
567

    
568
	/* check for expired entries */
569
	$idletimeout = 0;
570
	$timeout = 0;
571
	if (!empty($cpcfg['timeout']) && is_numeric($cpcfg['timeout'])) {
572
		$timeout = $cpcfg['timeout'] * 60;
573
	}
574

    
575
	if (!empty($cpcfg['idletimeout']) && is_numeric($cpcfg['idletimeout'])) {
576
		$idletimeout = $cpcfg['idletimeout'] * 60;
577
	}
578

    
579
	/* check for entries exceeding their traffic quota */
580
	$trafficquota = 0;
581
	if (!empty($cpcfg['trafficquota']) && is_numeric($cpcfg['trafficquota'])) {
582
		$trafficquota = $cpcfg['trafficquota'] * 1048576;
583
	}
584

    
585
	/* Is there any job to do? If we are in High Availability sync, are we in backup mode ? */
586
	if ((!$timeout && !$idletimeout && !$trafficquota && !isset($cpcfg['reauthenticate']) &&
587
	    !isset($cpcfg['radiussession_timeout']) && !isset($cpcfg['radiustraffic_quota']) &&
588
	    !isset($vcpcfg['enable']) && !isset($cpcfg['radacct_enable'])) ||
589
	    captiveportal_ha_is_node_in_backup_mode($cpzone)) {
590
		return;
591
	}
592

    
593

    
594
	/* Read database */
595
	/* NOTE: while this can be simplified in non radius case keep as is for now */
596
	$cpdb = captiveportal_read_db();
597

    
598
	$unsetindexes = array();
599
	$voucher_needs_sync = false;
600
	/*
601
	 * Snapshot the time here to use for calculation to speed up the process.
602
	 * If something is missed next run will catch it!
603
	 */
604
	$pruning_time = time();
605
	foreach ($cpdb as $cpentry) {
606
		$stop_time = $pruning_time;
607

    
608
		$timedout = false;
609
		$term_cause = 1;
610
		/* hard timeout or session_timeout from radius if enabled */
611
		if (isset($cpcfg['radiussession_timeout'])) {
612
			$utimeout = (is_numeric($cpentry[7])) ? $cpentry[7] : $timeout;
613
		} else {
614
			$utimeout = $timeout;
615
		}
616
		if ($utimeout) {
617
			if (($pruning_time - $cpentry[0]) >= $utimeout) {
618
				$timedout = true;
619
				$term_cause = 5; // Session-Timeout
620
				$logout_cause = 'SESSION TIMEOUT';
621
			}
622
		}
623

    
624
		/* Session-Terminate-Time */
625
		if (!$timedout && !empty($cpentry[9])) {
626
			if ($pruning_time >= $cpentry[9]) {
627
				$timedout = true;
628
				$term_cause = 5; // Session-Timeout
629
				$logout_cause = 'SESSION TIMEOUT';
630
			}
631
		}
632

    
633
		/* check if an idle_timeout has been set and if its set change the idletimeout to this value */
634
		$uidletimeout = (is_numeric($cpentry[8])) ? $cpentry[8] : $idletimeout;
635
		/* if an idle timeout is specified, get last activity timestamp from pf */
636
		if (!$timedout && $uidletimeout > 0) {
637
			$lastact = captiveportal_get_last_activity($cpentry[2]);
638
			/*	If the user has logged on but not sent any traffic they will never be logged out.
639
			 *	We "fix" this by setting lastact to the login timestamp.
640
			 */
641
			$lastact = $lastact ? $lastact : $cpentry[0];
642
			if ($lastact && (($pruning_time - $lastact) >= $uidletimeout)) {
643
				$timedout = true;
644
				$term_cause = 4; // Idle-Timeout
645
				$logout_cause = 'IDLE TIMEOUT';
646
				if (!isset($config['captiveportal'][$cpzone]['includeidletime'])) {
647
					$stop_time = $lastact;
648
				}
649
			}
650
		}
651

    
652
		/* if vouchers are configured, activate session timeouts */
653
		if (!$timedout && isset($vcpcfg['enable']) && !empty($cpentry[7])) {
654
			if ($pruning_time >= ($cpentry[0] + $cpentry[7])) {
655
				$timedout = true;
656
				$term_cause = 5; // Session-Timeout
657
				$logout_cause = 'SESSION TIMEOUT';
658
				$voucher_needs_sync = true;
659
			}
660
		}
661

    
662
		/* traffic quota, value retrieved from the radius attribute if the option is enabled */
663
		if (isset($cpcfg['radiustraffic_quota'])) {
664
			$utrafficquota = (is_numeric($cpentry[11])) ? $cpentry[11] : $trafficquota;
665
/*-----------------------------------------------------------------------------------------------------------*/
666
/* new code */
667
                        $intoverflow = true;  //to stop 32 bit overflow premature logout
668
/* new code */
669
/*-----------------------------------------------------------------------------------------------------------*/
670

    
671
		} else {
672
			$utrafficquota = $trafficquota;
673
		}
674

    
675
		if (!$timedout && $utrafficquota > 0) {
676
			$volume = getVolume($cpentry[2]);
677
			if (($volume['input_bytes'] + $volume['output_bytes']) > $utrafficquota) {
678

    
679
/* edited code original        $timedout = true;     */
680
/*-----------------------------------------------------------------------------------------------------------*/
681

    
682
				if ($intoverflow != true) {
683
                                   $timedout = true;
684
                                } else {
685
				  $timedout = false;  //to stop 32 bit overflow premature logout
686
                                }
687
/*-----------------------------------------------------------------------------------------------------------*/
688
/* edited code */
689

    
690
				$term_cause = 10; // NAS-Request
691
				$logout_cause = 'QUOTA EXCEEDED';
692
			}
693
		}
694

    
695
		if ($timedout) {
696
			captiveportal_disconnect($cpentry, $term_cause, $stop_time);
697
			captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], $logout_cause);
698
			$unsetindexes[] = $cpentry[5];
699
		}
700

    
701
		/* do periodic reauthentication? For Radius servers, send accounting updates? */
702
		if (!$timedout) {
703
			//Radius servers : send accounting
704
			if (isset($cpcfg['radacct_enable']) && $cpentry['authmethod'] === 'radius') {
705
				if (substr($cpcfg['reauthenticateacct'], 0, 9) === "stopstart") {
706
					/* stop and restart accounting */
707
					if ($cpcfg['reauthenticateacct'] === "stopstartfreeradius") {
708
/* Override the fixed time and use regular stop/start value
709
						$rastart_time = 0;
710
						$rastop_time = 60;
711

    
712
*/
713

    
714
						$rastart_time = 0;
715
						$rastop_time = $cpentry[10];
716
					} else {
717
/*						$rastart_time = $cpentry[0];
718
						$rastop_time = time();
719
*/
720
						$rastart_time = 0;
721
						$rastop_time = $cpentry[10];
722
					}
723

    
724
/*-----------------------------------------------------------------------------------------------------------*/
725
/* Override to use interim update from freeRadius GUI setting as stop/start frequency as well */
726

    
727
					$session_time = $pruning_time - $cpentry[0];
728
					if (!empty($cpentry[10]) && $cpentry[10] > 60) {
729
						$interval = $cpentry[10];
730
					} else {
731
						$interval = 0;
732
					}
733
					$past_interval_min = ($session_time > $interval);
734
					if ($interval != 0) {
735
						$within_interval = ($session_time % $interval >= 0 && $session_time % $interval <= 59);
736
					}
737
					if ($interval === 0 || ($interval > 0 && $past_interval_min && $within_interval)) {
738

    
739
/*-----------------------------------------------------------------------------------------------------------*/
740
					captiveportal_send_server_accounting('stop',
741
						$cpentry[1], // ruleno
742
						$cpentry[4], // username
743
						$cpentry[2], // clientip
744
						$cpentry[3], // clientmac
745
						$cpentry[5], // sessionid
746
						$rastart_time, // start time
747
						$rastop_time, // Stop Time
748
						10); // NAS Request
749
					/* XXX rewrite to C wrapper pfSense_pf_anchor_zerocnt() */
750
					captiveportal_anchor_zerocnt($cpentry[2], 'auth');
751
					if ($cpcfg['reauthenticateacct'] == "stopstartfreeradius") {
752
						/* Need to pause here or the FreeRADIUS server gets confused about packet ordering. */
753
/*						sleep(1); */
754
						usleep(250000);
755
					}
756
					captiveportal_send_server_accounting('start',
757
						$cpentry[1], // ruleno
758
						$cpentry[4], // username
759
						$cpentry[2], // clientip
760
						$cpentry[3], // clientmac
761
						$cpentry[5]); // sessionid
762
/*-----------------------------------------------------------------------------------------------------------*/
763

    
764
					}
765

    
766
/*-----------------------------------------------------------------------------------------------------------*/
767
				} else if ($cpcfg['reauthenticateacct'] == "interimupdate") {
768
					$session_time = $pruning_time - $cpentry[0];
769
					if (!empty($cpentry[10]) && $cpentry[10] > 60) {
770
						$interval = $cpentry[10];
771
					} else {
772
						$interval = 0;
773
					}
774
					$past_interval_min = ($session_time > $interval);
775
					if ($interval != 0) {
776
						$within_interval = ($session_time % $interval >= 0 && $session_time % $interval <= 59);
777
					}
778
					if ($interval === 0 || ($interval > 0 && $past_interval_min && $within_interval)) {
779
					captiveportal_send_server_accounting('update',
780
						$cpentry[1], // ruleno
781
						$cpentry[4], // username
782
						$cpentry[2], // clientip
783
						$cpentry[3], // clientmac
784
						$cpentry[5], // sessionid
785
						$cpentry[0]); // start time
786
					}
787
				}
788
			}
789

    
790
			/* check this user again */
791
			if (isset($cpcfg['reauthenticate']) && $cpentry['context'] !== 'voucher') {
792

    
793
/*-----------------------------------------------------------------------------------------------------------*/
794
/* Override to use interim update from freeRadius GUI setting as reauthenticate frequency as well */
795

    
796
					$session_time = $pruning_time - $cpentry[0];
797
					if (!empty($cpentry[10]) && $cpentry[10] > 60) {
798
						$interval = $cpentry[10];
799
					} else {
800
						$interval = 0;
801
					}
802
					$past_interval_min = ($session_time > $interval);
803
					if ($interval != 0) {
804
						$within_interval = ($session_time % $interval >= 0 && $session_time % $interval <= 59);
805
					}
806
					if ($interval === 0 || ($interval > 0 && $past_interval_min && $within_interval)) {
807

    
808
/*-----------------------------------------------------------------------------------------------------------*/
809

    
810

    
811

    
812
				$auth_result = captiveportal_authenticate_user(
813
					$cpentry[4], // username
814
					base64_decode($cpentry[6]), // password
815
					$cpentry[3], // clientmac
816
					$cpentry[2], // clientip
817
					$cpentry[1], // ruleno
818
					$cpentry['context']); // context
819
				if ($auth_result['result'] === false) {
820
					captiveportal_disconnect($cpentry, 17);
821
					captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "DISCONNECT - REAUTHENTICATION FAILED", $auth_list['reply_message']);
822
					$unsetindexes[] = $cpentry[5];
823
				} else if ($auth_result['result'] === true) {
824
					if ($cpentry['authmethod'] !== $auth_result['auth_method']) {
825
						// if the user got authenticated against another server type:  we update the database
826
						if (!empty($cpentry[5])) {
827
							captiveportal_update_entry($cpentry['sessionid'], $auth_result['auth_method'], 'authmethod');
828
							captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "CHANGED AUTHENTICATION SERVER", $auth_list['reply_message']);
829
						}
830
						// User was logged on a RADIUS server, but is now logged in by another server type : we send an accounting Stop
831
						if (isset($config['captiveportal'][$cpzone]['radacct_enable']) && $cpentry['authmethod'] == 'radius') {
832
							if ($cpcfg['reauthenticateacct'] === "stopstartfreeradius") {
833
								$rastart_time = 0;
834
								$rastop_time = 60;
835
							} else {
836
								$rastart_time = $cpentry[0];
837
								$rastop_time = time();
838
							}
839
							captiveportal_send_server_accounting('stop',
840
								$cpentry[1], // ruleno
841
								$cpentry[4], // username
842
								$cpentry[2], // clientip
843
								$cpentry[3], // clientmac
844
								$cpentry[5], // sessionid
845
								$rastart_time, // start time
846
								$rastop_time, // Stop Time
847
								3); // Lost Service
848
						// User was logged on a non-RADIUS Server but is now logged in by a RADIUS server : we send an accounting Start
849
						} else if(isset($config['captiveportal'][$cpzone]['radacct_enable']) && $auth_result['auth_method'] === 'radius') {
850
							captiveportal_send_server_accounting('start',
851
								$cpentry[1], // ruleno
852
								$cpentry[4], // username
853
								$cpentry[2], // clientip
854
								$cpentry[3], // clientmac
855
								$cpentry[5], // sessionid
856
								$cpentry[0]); // start_time
857
						}
858
					}
859
					captiveportal_reapply_attributes($cpentry, $auth_result['attributes']);
860
				}
861
/*-----------------------------------------------------------------------------------------------------------*/
862

    
863
					}
864

    
865
/*-----------------------------------------------------------------------------------------------------------*/
866
			}
867
		}
868
	}
869
	unset($cpdb);
870

    
871
	captiveportal_prune_old_automac();
872

    
873
	if ($voucher_needs_sync == true) {
874
		/* perform in-use vouchers expiration using check_reload_status */
875
		send_event("service sync vouchers");
876
	}
877

    
878
	/* write database */
879
	if (!empty($unsetindexes)) {
880
		captiveportal_remove_entries($unsetindexes);
881
	}
882
}
883

    
884
function captiveportal_prune_old_automac() {
885
	global $g, $config, $cpzone, $cpzoneid;
886

    
887
	if (is_array($config['captiveportal'][$cpzone]['passthrumac']) &&
888
	    isset($config['captiveportal'][$cpzone]['passthrumacadd'])) {
889
		$tmpvoucherdb = array();
890
		$writecfg = false;
891
		foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $eid => $emac) {
892
			if ($emac['logintype'] != "voucher") {
893
				continue;
894
			}
895
			if (isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) {
896
				if (isset($tmpvoucherdb[$emac['username']])) {
897
					$temac = config_get_path("captiveportal/{$cpzone}/passthrumac/{$tmpvoucherdb[$emac['username']]}");
898
					$pipes = captiveportal_get_dn_passthru_pipes($temac['mac']);
899
					if ($pipes) {
900
						captiveportal_ether_delete_entry($temac['mac'], 'auth');
901
					}
902
					$writecfg = true;
903
					captiveportal_logportalauth($temac['username'], $temac['mac'],
904
					    $temac['ip'], "DUPLICATE {$temac['username']} LOGIN - TERMINATING OLD SESSION");
905
					config_del_path("captiveportal/{$cpzone}/passthrumac/{$tmpvoucherdb[$emac['username']]}");
906
				}
907
				$tmpvoucherdb[$emac['username']] = $eid;
908
			}
909
		}
910
		unset($tmpvoucherdb);
911
		if ($writecfg === true) {
912
			write_config("Prune session for auto-added macs");
913
		}
914
	}
915
}
916

    
917
/* remove a single client according to the DB entry */
918
function captiveportal_disconnect($dbent, $term_cause = 1, $stop_time = null, $carp_loop = false) {
919
	global $g, $config, $cpzone, $cpzoneid;
920

    
921
	$stop_time = (empty($stop_time)) ? time() : $stop_time;
922

    
923
	/* this client needs to be deleted - remove pf anchor */
924
	if (isset($config['captiveportal'][$cpzone]['radacct_enable']) && $dbent['authmethod'] == 'radius') {
925
		if ($config['captiveportal'][$cpzone]['reauthenticateacct'] == "stopstartfreeradius") {
926
			/*
927
			 * Interim updates are on so the session time must be
928
			 * reported as the elapsed time since the previous
929
			 * interim update.
930
			 */
931
			$session_time = ($stop_time - $dbent[0]) % 60;
932
			$start_time = $stop_time - $session_time;
933
		} else {
934
			$start_time = $dbent[0];
935
		}
936
		captiveportal_send_server_accounting('stop',
937
			$dbent[1], // ruleno
938
			$dbent[4], // username
939
			$dbent[2], // clientip
940
			$dbent[3], // clientmac
941
			$dbent[5], // sessionid
942
			$start_time, // start time
943
			$stop_time, // stop time
944
			$term_cause); // Acct-Terminate-Cause
945
	}
946

    
947
	if (is_ipaddrv4($dbent[2])) {
948
		/*
949
		 * Delete client's anchor entry from auth anchor
950
		 */
951
		$cpsession = captiveportal_isip_logged($dbent[2]);
952
		if (!empty($cpsession)) {
953
			$host = array();
954
			$host['ip'] = $dbent[2];
955
			if (!isset($config['captiveportal'][$cpzone]['nomacfilter'])) {
956
				$host['mac'] = $dbent[3];
957
			}
958
			captiveportal_ether_delete_entry($host, 'auth');
959
		}
960
		/* XXX: Redundant?! Ensure all pf(4) states are killed. */
961
		$_gb = @pfSense_kill_states(utf8_encode($dbent[2]));
962
		$_gb = @pfSense_kill_srcstates(utf8_encode($dbent[2]));
963
	}
964

    
965
	// XMLRPC Call over to the backup node if necessary
966
	if (captiveportal_xmlrpc_sync_get_details($syncip, $syncport,
967
	    $syncuser, $syncpass, $carp_loop)) {
968
		$rpc_client = new pfsense_xmlrpc_client();
969
		$rpc_client->setConnectionData($syncip, $syncport, $syncuser, $syncpass);
970
		$rpc_client->set_noticefile("CaptivePortalUserSync");
971
		$arguments = array(
972
			'sessionid' => $dbent[5],
973
			'term_cause' => $term_cause,
974
			'stop_time' => $stop_time
975
		);
976

    
977
		$rpc_client->xmlrpc_method('captive_portal_sync',
978
			array(
979
				'op' => 'disconnect_user',
980
				'zone' => $cpzone,
981
				'session' => base64_encode(serialize($arguments))
982
			)
983
		);
984
	}
985
	return true;
986
}
987

    
988
/* remove a single client by sessionid */
989
function captiveportal_disconnect_client($sessionid, $term_cause = 1, $logoutReason = "LOGOUT") {
990
	global $g, $config;
991

    
992
	$sessionid = SQLite3::escapeString($sessionid);
993
	/* read database */
994
	$result = captiveportal_read_db("WHERE sessionid = '{$sessionid}'");
995

    
996
	/* find entry */
997
	if (!empty($result)) {
998
		foreach ($result as $cpentry) {
999
			captiveportal_disconnect($cpentry, $term_cause);
1000
			captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "DISCONNECT");
1001
		}
1002
		captiveportal_remove_entries(array($sessionid));
1003
	}
1004
}
1005

    
1006
/* remove all clients */
1007
function captiveportal_disconnect_all($term_cause = 6, $logoutReason = "DISCONNECT", $carp_loop = false) {
1008
	global $g, $config, $cpzone, $cpzoneid;
1009

    
1010
	if (captiveportal_xmlrpc_sync_get_details($syncip, $syncport, $syncuser, $syncpass, $carp_loop)) {
1011
		$rpc_client = new pfsense_xmlrpc_client();
1012
		$rpc_client->setConnectionData($syncip, $syncport, $syncuser, $syncpass);
1013
		$rpc_client->set_noticefile("CaptivePortalUserSync");
1014
		$arguments = array(
1015
			'term_cause' => $term_cause,
1016
			'logout_reason' => $logoutReason
1017
		);
1018

    
1019
		$rpc_client->xmlrpc_method('captive_portal_sync',
1020
			array(
1021
				'op' => 'disconnect_all',
1022
				'zone' => $cpzone,
1023
				'arguments' => base64_encode(serialize($arguments))
1024
			)
1025
		);
1026
	}
1027
	/* check if we're pruning old entries and eventually wait */
1028
	$rcprunelock = try_lock("rcprunecaptiveportal{$cpzone}", 15);
1029

    
1030
	/* if we still don't have the lock, unlock forcefully and take it */
1031
	if (!$rcprunelock) {
1032
		log_error("CP zone ${cpzone}: could not obtain the lock for more than 15 seconds, lock taken forcefully to disconnect all users");
1033
		unlock_force("rcprunecaptiveportal{$cpzone}");
1034
		$rcprunelock = lock("rcprunecaptiveportal{$cpzone}", LOCK_EX);
1035
	}
1036

    
1037
	/* take a lock so new users won't be able to log in */
1038
	$cpdblck = lock("captiveportaldb{$cpzone}", LOCK_EX);
1039

    
1040
	captiveportal_radius_stop_all($term_cause, $logoutReason);
1041

    
1042
	/* reinit captiveportal pipes and anchors */
1043
	captiveportal_init_rules(true);
1044

    
1045
	/* remove users from the database */
1046
	$cpdb = captiveportal_read_db();
1047
	$unsetindexes = array_column($cpdb,5);
1048
	if (!empty($unsetindexes)) {
1049
		// High Availability : do not sync removed entries
1050
		captiveportal_remove_entries($unsetindexes, true);
1051
	}
1052

    
1053
	unlock($cpdblck);
1054
	unlock($rcprunelock);
1055
	return true;
1056
}
1057

    
1058
/* send RADIUS acct stop for all current clients connected with RADIUS servers */
1059
function captiveportal_radius_stop_all($term_cause = 6, $logoutReason = "DISCONNECT") {
1060
	global $g, $config, $cpzone, $cpzoneid;
1061

    
1062
	$cpdb = captiveportal_read_db();
1063

    
1064
	$radacct = isset($config['captiveportal'][$cpzone]['radacct_enable']) ? true : false;
1065
	foreach ($cpdb as $cpentry) {
1066
		if ($cpentry['authmethod'] === 'radius' && $radacct) {
1067
			if ($config['captiveportal'][$cpzone]['reauthenticateacct'] == "stopstartfreeradius") {
1068
				$session_time = (time() - $cpentry[0]) % 60;
1069
				$start_time = time() - $session_time;
1070
			} else {
1071
				$start_time = $cpentry[0];
1072
			}
1073
			captiveportal_send_server_accounting('stop',
1074
				$cpentry[1], // ruleno
1075
				$cpentry[4], // username
1076
				$cpentry[2], // clientip
1077
				$cpentry[3], // clientmac
1078
				$cpentry[5], // sessionid
1079
				$start_time, // start time
1080
				$stop_time, // stop time
1081
				$term_cause); // Acct-Terminate-Cause
1082
		}
1083
		captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], $logoutReason);
1084
	}
1085
	unset($cpdb);
1086
}
1087

    
1088
function captiveportal_passthrumac_delete_entry($macent) {
1089
	global $g, $cpzone, $config;
1090

    
1091
	$host = str_replace("/", "_", str_replace(":", "", $macent['mac']));
1092
	$cpzoneprefix = CPPREFIX . $config['captiveportal'][$cpzone]['zoneid'];
1093

    
1094
	if ($macent['action'] == 'pass') {
1095
		$pipes = captiveportal_get_dn_passthru_pipes($macent['mac']);
1096
		if (!empty($pipes)) {
1097
			captiveportal_pipes_delete($pipes);
1098
		}
1099
	} else {
1100
		/* no rules on passthru block */
1101
		return;
1102
	}
1103

    
1104
	pfSense_pf_cp_flush("{$cpzoneprefix}_passthrumac/{$host}", "ether");
1105
}
1106

    
1107
function captiveportal_passthrumac_configure($startindex = 0, $stopindex = 0) {
1108
	global $config, $g, $cpzone;
1109

    
1110
	if (is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
1111
		if ($stopindex > 0) {
1112
			for ($idx = $startindex; $idx <= $stopindex; $idx++) {
1113
				if (isset($config['captiveportal'][$cpzone]['passthrumac'][$idx])) {
1114
					captiveportal_ether_configure_entry($config['captiveportal'][$cpzone]['passthrumac'][$idx], 'passthrumac');
1115
				}
1116
			}
1117
		} else {
1118
			$nentries = count($config['captiveportal'][$cpzone]['passthrumac']);
1119
			if ($nentries > 2000) {
1120
				$nloops = $nentries / 1000;
1121
				$remainder= $nentries % 1000;
1122
				for ($i = 0; $i < $nloops; $i++) {
1123
					mwexec_bg("/usr/local/sbin/fcgicli -f /etc/rc.captiveportal_configure_mac -d \"cpzone={$cpzone}&startidx=" . ($i * 1000) . "&stopidx=" . ((($i+1) * 1000) - 1) . "\"");
1124
				}
1125
				if ($remainder > 0) {
1126
					mwexec_bg("/usr/local/sbin/fcgicli -f /etc/rc.captiveportal_configure_mac -d \"cpzone={$cpzone}&startidx=" . ($i * 1000) . "&stopidx=" . (($i* 1000) + $remainder) ."\"");
1127
				}
1128
			} else {
1129
				foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $macent) {
1130
					captiveportal_ether_configure_entry($macent, 'passthrumac');
1131
				}
1132
			}
1133
		}
1134
	}
1135
}
1136

    
1137
function captiveportal_passthrumac_findbyname($username) {
1138
	global $config, $cpzone;
1139

    
1140
	if (is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
1141
		foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $macent) {
1142
			if ($macent['username'] == $username) {
1143
				return $macent;
1144
			}
1145
		}
1146
	}
1147
	return NULL;
1148
}
1149

    
1150
function captiveportal_ether_delete_entry($hostent, $anchor = 'allowedhosts') {
1151
	global $g, $cpzone, $config;
1152

    
1153
	$cpzoneprefix = CPPREFIX . $config['captiveportal'][$cpzone]['zoneid'];
1154

    
1155
	if (!empty($hostent['sn'])) {
1156
		$host = $hostent['ip'] . '_' . $hostent['sn'];
1157
	} else {
1158
		$host = $hostent['ip'] . '_32';
1159
	}
1160

    
1161
	$pipes = pfSense_pf_cp_get_eth_pipes("{$cpzoneprefix}_{$anchor}/{$host}");
1162
	if (!empty($pipes)) {
1163
		captiveportal_pipes_delete($pipes);
1164
	}
1165
	/* flush anchor rules */
1166
	pfSense_pf_cp_flush("{$cpzoneprefix}_{$anchor}/{$host}", "ether");
1167
}
1168

    
1169
function captiveportal_allowedhostname_configure() {
1170
	global $config, $g, $cpzone, $cpzoneid;
1171

    
1172
	if (!is_array($config['captiveportal'][$cpzone]['allowedhostname'])) {
1173
		return false;
1174
	}
1175

    
1176
	$cp_filterdns_conf = "";
1177
	foreach ($config['captiveportal'][$cpzone]['allowedhostname'] as $id => $hostnameent) {
1178
		$cp_filterdns_conf .= captiveportal_allowedhostname_configure_entry($hostnameent, $id);
1179
	}
1180
	$cp_filterdns_filename = "{$g['varetc_path']}/filterdns-{$cpzone}-captiveportal.conf";
1181
	if ((!file_exists($cp_filterdns_filename) && !empty($cp_filterdns_conf)) ||
1182
	    (file_exists($cp_filterdns_filename) && ($cp_filterdns_conf != file_get_contents($cp_filterdns_filename)))) {
1183
		@file_put_contents($cp_filterdns_filename, $cp_filterdns_conf);
1184
		filter_configure();
1185
		captiveportal_filterdns_configure();
1186
	}
1187
}
1188

    
1189
function captiveportal_filterdns_configure() {
1190
	global $config, $g, $cpzone, $cpzoneid;
1191

    
1192
	$cp_filterdns_filename = g_get('varetc_path') .
1193
	    "/filterdns-{$cpzone}-captiveportal.conf";
1194

    
1195
	if (isset($config['captiveportal'][$cpzone]['enable']) &&
1196
	    is_array($config['captiveportal'][$cpzone]['allowedhostname']) &&
1197
	    file_exists($cp_filterdns_filename) &&
1198
	    !empty(file_get_contents($cp_filterdns_filename))) {
1199
		if (isvalidpid(g_get('varrun_path') .
1200
		    "/filterdns-{$cpzone}-cpah.pid")) {
1201
			sigkillbypid(g_get('varrun_path') .
1202
			    "/filterdns-{$cpzone}-cpah.pid", "HUP");
1203
		} else {
1204
			mwexec("/usr/local/sbin/filterdns -p " .
1205
			    "{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid" .
1206
			    " -i 300 -c {$cp_filterdns_filename} -d 1");
1207
		}
1208
	} else {
1209
		killbypid("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid");
1210
		@unlink("{$g['varrun_path']}/filterdns-{$cpzone}-cpah.pid");
1211
	}
1212
}
1213

    
1214
function captiveportal_allowedip_configure() {
1215
	global $config, $g, $cpzone;
1216

    
1217
	if (is_array($config['captiveportal'][$cpzone]['allowedip'])) {
1218
		foreach ($config['captiveportal'][$cpzone]['allowedip'] as $ipent) {
1219
			captiveportal_allowedip_configure_entry($ipent);
1220
		}
1221
	}
1222
}
1223

    
1224
/* get last activity timestamp given client IP address */
1225
function captiveportal_get_last_activity($ip) {
1226
	global $config, $cpzone;
1227

    
1228
	$cpzoneprefix = CPPREFIX . $config['captiveportal'][$cpzone]['zoneid'];
1229
	$anchor = $cpzoneprefix . '_auth';
1230

    
1231
	$active_times = pfSense_pf_cp_get_eth_last_active("{$anchor}/{$ip}_32");
1232
	$time = 0;
1233
	if (!empty($active_times)) {
1234
		foreach ($active_times as $active_time) {
1235
			if ( $active_time > $time)
1236
				$time = $active_time;
1237
	   }
1238
	}
1239

    
1240
	return $time;
1241
}
1242

    
1243

    
1244
/* log successful captive portal authentication to syslog */
1245
/* part of this code from php.net */
1246
function captiveportal_logportalauth($user, $mac, $ip, $status, $message = null) {
1247
	// Log it
1248
	if (!$message) {
1249
		$message = "{$status}: {$user}, {$mac}, {$ip}";
1250
	} else {
1251
		$message = trim($message);
1252
		$message = "{$status}: {$user}, {$mac}, {$ip}, {$message}";
1253
	}
1254
	captiveportal_syslog($message);
1255
}
1256

    
1257
/* log simple messages to syslog */
1258
function captiveportal_syslog($message) {
1259
	global $cpzone;
1260

    
1261
	$message = trim($message);
1262
	$message = "Zone: {$cpzone} - {$message}";
1263
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
1264
	// Log it
1265
	syslog(LOG_INFO, $message);
1266
	closelog();
1267
}
1268

    
1269
/* Authenticate users using Authentication Backend */
1270
function captiveportal_authenticate_user(&$login = '', &$password = '', $clientmac = '', $clientip = '', $pipeno = 'null', $context = 'first') {
1271
	global $g, $config, $cpzone;
1272
	$cpcfg = config_get_path("captiveportal/{$cpzone}");
1273

    
1274
	$login_status = 'FAILURE';
1275
	$login_msg = gettext('Invalid credentials specified');
1276
	$reply_attributes = array();
1277
	$auth_method = '';
1278
	$auth_result = null;
1279

    
1280
	/*
1281
	Management of the reply Message (reason why the authentication failed) :
1282
	multiple authentication servers can be used, so multiple reply messages could theoretically be returned.
1283
	But only one message is returned (the most important one).
1284
	The return value of authenticate_user() define how important messages are :
1285
		- Reply message of a successful auth is more important than reply message of
1286
		a user failed auth(invalid credentials/authorization)
1287

    
1288
		- Reply message of a user failed auth is more important than reply message of
1289
		a server failed auth (unable to contact server)
1290

    
1291
		- When multiple user failed auth are encountered, messages returned by remote servers
1292
		(eg. reply in RADIUS Access-Reject) are more important than pfSense error messages.
1293

    
1294
	The $authlevel variable is a flag indicating the status of authentication
1295
	0 = failed server auth
1296
	1 = failed user auth
1297
	2 = failed user auth with custom server reply received
1298
	3 = successful auth
1299
	*/
1300
	$authlevel = 0;
1301

    
1302
	/* Getting authentication servers from captiveportal configuration */
1303
	$auth_servers = array();
1304

    
1305
	if ($cpcfg['auth_method'] === 'none') {
1306
		$auth_servers[] = array('type' => 'none');
1307
	} else {
1308
		if ($context === 'second') {
1309
			$fullauthservers = explode(",", $cpcfg['auth_server2']);
1310
		} else {
1311
			$fullauthservers = explode(",", $cpcfg['auth_server']);
1312
		}
1313

    
1314
		foreach ($fullauthservers as $authserver) {
1315
			if (strpos($authserver, ' - ') !== false) {
1316
				$authserver = explode(' - ', $authserver);
1317
				array_shift($authserver);
1318
				$authserver = implode(' - ', $authserver);
1319

    
1320
				if (auth_get_authserver($authserver) !== null) {
1321
					$auth_servers[] = auth_get_authserver($authserver);
1322
				} else {
1323
					log_error("Zone: {$cpzone} - Captive portal was unable to find the settings of the server '{$authserver}' used for authentication !");
1324
				}
1325
			}
1326
		}
1327
	}
1328

    
1329
	/* Unable to find the any authentication server config - shouldn't happen! - bail out */
1330
	if (count($auth_servers) === 0) {
1331
		log_error("Zone: {$cpzone} - No valid server could be used for authentication.");
1332
		$login_msg = gettext("Internal Error");
1333
	} else {
1334
		foreach ($auth_servers as $authcfg) {
1335
			if ($authlevel < 3) {
1336
				$radmac_error = false;
1337
				$attributes = array("nas_identifier" => empty($cpcfg["radiusnasid"]) ? "CaptivePortal-{$cpzone}" : $cpcfg["radiusnasid"],
1338
					"nas_port_type" => RADIUS_ETHERNET,
1339
					"nas_port" => $pipeno,
1340
					"framed_ip" => $clientip);
1341
				if (mac_format($clientmac) !== null) {
1342
					$attributes["calling_station_id"] = mac_format($clientmac);
1343
				}
1344

    
1345
				$result = null;
1346
				$status = null;
1347
				$msg = null;
1348

    
1349
				/* Radius MAC authentication */
1350
				if ($context === 'radmac' && $clientmac) {
1351
					if ($authcfg['type'] === 'radius') {
1352
						$login = mac_format($clientmac);
1353
						$status = "MACHINE LOGIN";
1354
					} else {
1355
						/* Trying to perform a Radius MAC authentication on a non-radius server - shouldn't happen! - bail out */
1356
						$msg = gettext("Internal Error");
1357
						log_error("Zone: {$cpzone} - Trying to perform RADIUS MAC authentication on a non-RADIUS server !");
1358
						$radmac_error = true;
1359
						$result = null;
1360
					}
1361
				}
1362

    
1363
				if (!$radmac_error) {
1364
					if ($authcfg['type'] === 'none') {
1365
						$result = true;
1366
					} else {
1367
						$result = authenticate_user($login, $password, $authcfg, $attributes);
1368
					}
1369

    
1370
					if (!empty($attributes['error_message'])) {
1371
						$msg = $attributes['error_message'];
1372
					}
1373

    
1374
					if ($authcfg['type'] == 'Local Auth' && $result && isset($cpcfg['localauth_priv'])) {
1375
						if (!userHasPrivilege(getUserEntry($login), "user-services-captiveportal-login")) {
1376
							$result = false;
1377
							$msg = gettext("Access Denied");
1378
						}
1379
					}
1380
					if ($context === 'radmac' && $result === null && empty($attributes['reply_message'])) {
1381
						$msg = gettext("RADIUS MAC Authentication Failed.");
1382
					}
1383

    
1384
/* ----------------------------------------------------- New Code  ------------------------------------------- */
1385

    
1386
/* Check to see if this user is a monthly/router user (vlan60) and deny access to DATA (vlan50) and vice versa*/
1387

    
1388
$user_file = "/var/log/radacct/datacounter/monthly/max-octets-";
1389
$user_file .= $login;
1390
if (file_exists($user_file) && ($cpzone == "vlan50")) {
1391
      $status = "FAILURE";
1392
      $result = false;
1393
}
1394
$user_file = "/var/log/radacct/datacounter/forever/max-octets-";
1395
$user_file .= $login;
1396
if (file_exists($user_file) && ($cpzone == "vlan60")) {
1397
      $status = "FAILURE";
1398
      $result = false;
1399
}
1400

    
1401
/* --------------------------------------------------------------------------------------------------------------- */
1402

    
1403
					if (empty($status)) {
1404
						if ($result === true) {
1405
							$status = "ACCEPT";
1406
						} elseif ($result === null) {
1407
							$status = "ERROR";
1408
						} else {
1409
							$status = "FAILURE";
1410
						}
1411
					}
1412

    
1413
					if ($context === 'radmac' && $login == mac_format($clientmac) || $authcfg['type'] === 'none' && empty($login)) {
1414
						$login = "unauthenticated";
1415
					}
1416
				}
1417
				// We determine a flag
1418
				if ($result === true) {
1419
					$val = 3;
1420
				} elseif ($result === false && !empty($attributes['reply_message'])) {
1421
					$val = 2;
1422
					$msg = $attributes['reply_message'];
1423
				} elseif ($result === false) {
1424
					$val = 1;
1425
				} elseif ($result === null) {
1426
					$val = 0;
1427
				}
1428

    
1429
				if ($val >= $authlevel) {
1430
					$authlevel = $val;
1431
					$auth_method = $authcfg['type'];
1432
					$login_status = $status;
1433
					$login_msg = $msg;
1434
					$reply_attributes = $attributes;
1435
					$auth_result = $result;
1436
				}
1437
			}
1438
		}
1439
	}
1440

    
1441
	return array('result'=>$auth_result, 'attributes'=>$reply_attributes, 'auth_method' =>$auth_method, 'login_status'=> $login_status, 'login_message' => $login_msg);
1442
}
1443

    
1444
function captiveportal_opendb() {
1445
	global $g, $config, $cpzone, $cpzoneid;
1446

    
1447
	$db_path = "{$g['vardb_path']}/captiveportal{$cpzone}.db";
1448
	$createquery = "CREATE TABLE IF NOT EXISTS captiveportal (" .
1449
				"allow_time INTEGER, pipeno INTEGER, ip TEXT, mac TEXT, username TEXT, " .
1450
				"sessionid TEXT, bpassword TEXT, session_timeout INTEGER, idle_timeout INTEGER, " .
1451
				"session_terminate_time INTEGER, interim_interval INTEGER, traffic_quota INTEGER, " .
1452
				"bw_up INTEGER, bw_down INTEGER, authmethod TEXT, context TEXT); " .
1453
			"CREATE UNIQUE INDEX IF NOT EXISTS idx_active ON captiveportal (sessionid, username); " .
1454
			"CREATE INDEX IF NOT EXISTS user ON captiveportal (username); " .
1455
			"CREATE INDEX IF NOT EXISTS ip ON captiveportal (ip); " .
1456
			"CREATE INDEX IF NOT EXISTS starttime ON captiveportal (allow_time)";
1457

    
1458
	try {
1459
		$DB = new SQLite3($db_path);
1460
		$DB->busyTimeout(60000);
1461
	} catch (Exception $e) {
1462
		captiveportal_syslog("Could not open {$db_path} as an sqlite database for {$cpzone}. Error message: " . $e->getMessage() . " -- Trying again.");
1463
		unlink_if_exists($db_path);
1464
		try {
1465
			$DB = new SQLite3($db_path);
1466
			$DB->busyTimeout(60000);
1467
		} catch (Exception $e) {
1468
			captiveportal_syslog("Still could not open {$db_path} as an sqlite database for {$cpzone}. Error message: " . $e->getMessage() . " -- Remove the database file manually and ensure there is enough free space.");
1469
			return;
1470
		}
1471
	}
1472

    
1473
	if (!$DB) {
1474
		captiveportal_syslog("Could not open {$db_path} as an sqlite database for {$cpzone}. Error message: {$DB->lastErrorMsg()}. Trying again.");
1475
		unlink_if_exists($db_path);
1476
		$DB = new SQLite3($db_path);
1477
		$DB->busyTimeout(60000);
1478
		if (!$DB) {
1479
			captiveportal_syslog("Still could not open {$db_path} as an sqlite database for {$cpzone}. Error message: {$DB->lastErrorMsg()}. Remove the database file manually and ensure there is enough free space.");
1480
			return;
1481
		}
1482
	}
1483

    
1484
	if (! $DB->exec($createquery)) {
1485
		captiveportal_syslog("Error during table {$cpzone} creation. Error message: {$DB->lastErrorMsg()}. Resetting and trying again.");
1486

    
1487
		/* If unable to initialize the database, reset and try again. */
1488
		$DB->close();
1489
		unset($DB);
1490
		unlink_if_exists($db_path);
1491
		$DB = new SQLite3($db_path);
1492
		$DB->busyTimeout(60000);
1493
		if ($DB->exec($createquery)) {
1494
			captiveportal_syslog("Successfully reinitialized tables for {$cpzone} -- database has been reset.");
1495
			if (!is_numericint($cpzoneid)) {
1496
				if (is_array($config['captiveportal'])) {
1497
					foreach ($config['captiveportal'] as $cpkey => $cp) {
1498
						if ($cpzone == $cpkey) {
1499
							$cpzoneid = $cp['zoneid'];
1500
						}
1501
					}
1502
				}
1503
			}
1504
			if (is_numericint($cpzoneid)) {
1505
				captiveportal_delete_rules(array(), true);
1506
				filter_configure();
1507
				captiveportal_syslog("Flushed tables for {$cpzone} after database reset.");
1508
			}
1509
		} else {
1510
			captiveportal_syslog("Still unable to create tables for {$cpzone}. Error message: {$DB->lastErrorMsg()}. Remove the database file manually and try again.");
1511
		}
1512
	}
1513

    
1514
	return $DB;
1515
}
1516

    
1517
/* read captive portal DB into array */
1518
function captiveportal_read_db($query = "") {
1519
	$cpdb = array();
1520

    
1521
	$DB = captiveportal_opendb();
1522
	if ($DB) {
1523
		$response = $DB->query("SELECT * FROM captiveportal {$query}");
1524
		if ($response != FALSE) {
1525
			while ($row = $response->fetchArray()) {
1526
				$cpdb[] = $row;
1527
			}
1528
		}
1529
		$DB->close();
1530
	}
1531

    
1532
	return $cpdb;
1533
}
1534

    
1535
function captiveportal_remove_entries($remove, $carp_loop = false) {
1536
	global $cpzone;
1537

    
1538
	if (!is_array($remove) || empty($remove)) {
1539
		return;
1540
	}
1541

    
1542
	$query = "DELETE FROM captiveportal WHERE sessionid in (";
1543
	foreach ($remove as $idx => $unindex) {
1544
		$query .= "'{$unindex}'";
1545
		if ($idx < (count($remove) - 1)) {
1546
			$query .= ",";
1547
		}
1548
	}
1549
	$query .= ")";
1550
	captiveportal_write_db($query);
1551

    
1552
	if (captiveportal_xmlrpc_sync_get_details($syncip, $syncport, $syncuser, $syncpass, $carp_loop)) {
1553
		$rpc_client = new pfsense_xmlrpc_client();
1554
		$rpc_client->setConnectionData($syncip, $syncport, $syncuser, $syncpass);
1555
		$rpc_client->set_noticefile("CaptivePortalUserSync");
1556
		$rpc_client->xmlrpc_method('captive_portal_sync',
1557
			array(
1558
				'op' => 'remove_entries',
1559
				'zone' => $cpzone,
1560
				'entries' => base64_encode(serialize($remove))
1561
			)
1562
		);
1563
	}
1564
	return true;
1565
}
1566

    
1567
/* write captive portal DB */
1568
function captiveportal_write_db($queries) {
1569
	global $g;
1570

    
1571
	if (is_array($queries)) {
1572
		$query = implode(";", $queries);
1573
	} else {
1574
		$query = $queries;
1575
	}
1576

    
1577
	$DB = captiveportal_opendb();
1578
	if ($DB) {
1579
		$DB->exec("BEGIN TRANSACTION");
1580
		$result = $DB->exec($query);
1581
		if (!$result) {
1582
			captiveportal_syslog("Trying to modify DB returned error: {$DB->lastErrorMsg()}");
1583
		} else {
1584
			$DB->exec("END TRANSACTION");
1585
		}
1586
		$DB->close();
1587
		return $result;
1588
	} else {
1589
		return true;
1590
	}
1591
}
1592

    
1593
function captiveportal_write_elements() {
1594
	global $g, $config, $cpzone;
1595

    
1596
	$cpcfg = config_get_path("captiveportal/{$cpzone}");
1597

    
1598
	if (!is_dir(g_get('captiveportal_element_path'))) {
1599
		@mkdir(g_get('captiveportal_element_path'));
1600
	}
1601

    
1602
	if (is_array($cpcfg['element'])) {
1603
		foreach ($cpcfg['element'] as $data) {
1604
			/* Do not attempt to decode or write out empty files. */
1605
			if (isset($data['nocontent'])) {
1606
					continue;
1607
			}
1608
			if (empty($data['content']) || empty(base64_decode($data['content']))) {
1609
				unlink_if_exists("{$g['captiveportal_element_path']}/{$data['name']}");
1610
				touch("{$g['captiveportal_element_path']}/{$data['name']}");
1611
			} elseif (!@file_put_contents("{$g['captiveportal_element_path']}/{$data['name']}", base64_decode($data['content']))) {
1612
				printf(gettext('Error: cannot open \'%1$s\' in captiveportal_write_elements()%2$s'), $data['name'], "\n");
1613
				return 1;
1614
			}
1615
			if (!file_exists("{$g['captiveportal_path']}/{$data['name']}")) {
1616
				@symlink("{$g['captiveportal_element_path']}/{$data['name']}", "{$g['captiveportal_path']}/{$data['name']}");
1617
			}
1618
		}
1619
	}
1620

    
1621
	return 0;
1622
}
1623

    
1624
function captiveportal_free_dnrules($rulenos_start = 2000,
1625
    $rulenos_range_max = 64500, $dry_run = false, $clear_auth_pipes = true) {
1626
	global $g, $cpzone;
1627

    
1628
	$removed_pipes = array();
1629

    
1630
	if (!file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1631
		return $removed_pipes;
1632
	}
1633

    
1634
	if (!$dry_run) {
1635
		$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1636
	}
1637

    
1638
	$rules = unserialize(file_get_contents(
1639
	    "{$g['vardb_path']}/captiveportaldn.rules"));
1640
	$ridx = $rulenos_start;
1641
	while ($ridx < $rulenos_range_max) {
1642
		if (substr($rules[$ridx], 0, strlen($cpzone . '_')) == $cpzone . '_') {
1643
			if (!$clear_auth_pipes && substr($rules[$ridx], 0, strlen($cpzone . '_auth')) == $cpzone . '_auth') {
1644
				$ridx += 2;
1645
			} else {
1646
				if (!$dry_run) {
1647
					$rules[$ridx] = false;
1648
				}
1649
				$removed_pipes[] = $ridx;
1650
				$ridx++;
1651
				if (!$dry_run) {
1652
					$rules[$ridx] = false;
1653
				}
1654
				$removed_pipes[] = $ridx;
1655
				$ridx++;
1656
			}
1657
		} else {
1658
			$ridx += 2;
1659
		}
1660
	}
1661

    
1662
	if (!$dry_run) {
1663
		file_put_contents("{$g['vardb_path']}/captiveportaldn.rules",
1664
		    serialize($rules));
1665
		unlock($cpruleslck);
1666
	}
1667

    
1668
	unset($rules);
1669

    
1670
	return $removed_pipes;
1671
}
1672

    
1673
function captiveportal_reserve_ruleno($ruleno) {
1674
	global $g, $cpzone;
1675

    
1676
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1677
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1678
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1679
	} else {
1680
		$rules = array_pad(array(), 64500, false);
1681
	}
1682
	$rules[$ruleno] = $cpzone . '_auth';
1683
	$ruleno++;
1684
	$rules[$ruleno] = $cpzone . '_auth';
1685

    
1686
	file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1687
	unlock($cpruleslck);
1688
	unset($rules);
1689

    
1690
	return $ruleno;
1691
}
1692

    
1693
function captiveportal_get_next_dn_ruleno($rule_type = 'default', $rulenos_start = 2000, $rulenos_range_max = 64500, $check_only = false) {
1694
	global $config, $g, $cpzone;
1695

    
1696
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1697
	$ruleno = 0;
1698
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1699
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1700
		$ridx = $rulenos_start;
1701
		while ($ridx < $rulenos_range_max) {
1702
			if (empty($rules[$ridx])) {
1703
				$ruleno = $ridx;
1704
				$rules[$ridx] = $cpzone . '_' . $rule_type;
1705
				$ridx++;
1706
				$rules[$ridx] = $cpzone . '_' . $rule_type;
1707
				break;
1708
			} else {
1709
				$ridx += 2;
1710
			}
1711
		}
1712
	} else {
1713
		$rules = array_pad(array(), $rulenos_range_max, false);
1714
		$ruleno = $rulenos_start;
1715
		$rules[$rulenos_start] = $cpzone . '_' . $rule_type;
1716
		$rulenos_start++;
1717
		$rules[$rulenos_start] = $cpzone . '_' . $rule_type;
1718
	}
1719
	if (!$check_only) {
1720
		file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1721
	}
1722
	unlock($cpruleslck);
1723
	unset($rules);
1724

    
1725
	return $ruleno;
1726
}
1727

    
1728
function captiveportal_free_dn_rulenos($rulenos) {
1729
	global $config, $g;
1730

    
1731
	$cpruleslck = lock("captiveportalrulesdn", LOCK_EX);
1732
	if (file_exists("{$g['vardb_path']}/captiveportaldn.rules")) {
1733
		$rules = unserialize(file_get_contents("{$g['vardb_path']}/captiveportaldn.rules"));
1734
		foreach ($rulenos as $ruleno) {
1735
			$rules[$ruleno] = false;
1736
		}
1737
		file_put_contents("{$g['vardb_path']}/captiveportaldn.rules", serialize($rules));
1738
		unset($rules);
1739
	}
1740
	unlock($cpruleslck);
1741
}
1742

    
1743
function captiveportal_get_dn_passthru_pipes($mac, $anchor = 'passthrumac') {
1744
	global $config, $g, $cpzone, $cpzoneid;
1745

    
1746
	$cpcfg = config_get_path("captiveportal/{$cpzone}");
1747
	$cpzoneprefix = CPPREFIX . $cpcfg['zoneid'];
1748
	if (!isset($cpcfg['enable'])) {
1749
		return NULL;
1750
	}
1751

    
1752
	$host = str_replace(":", "", $mac);
1753
	$pipes = pfSense_pf_cp_get_eth_pipes("{$cpzoneprefix}_{$anchor}/$host");
1754

    
1755
	return $pipes;
1756
}
1757

    
1758
/**
1759
 * This function will calculate the traffic produced by a client
1760
 * based on its firewall rule
1761
 *
1762
 * Point of view: NAS
1763
 *
1764
 * Input means: from the client
1765
 * Output means: to the client
1766
 *
1767
 */
1768

    
1769
function getVolume($ip) {
1770
	global $g, $config, $cpzone;
1771

    
1772
	$reverse = isset($config['captiveportal'][$cpzone]['reverseacct']) ? true : false;
1773
	$volume = array();
1774
	// Initialize vars properly, since we don't want NULL vars
1775
	$volume['input_pkts'] = $volume['input_bytes'] = 0;
1776
	$volume['output_pkts'] = $volume['output_bytes'] = 0;
1777

    
1778
	/* no needs to check allowedip */
1779
	$cpzoneprefix = CPPREFIX . $config['captiveportal'][$cpzone]['zoneid'];
1780
	$anchor = $cpzoneprefix . '_auth';
1781

    
1782
	// It is presumed that a list of arrays is returned, each containing rule direction, and packet and bytes counters.
1783
	$result = pfSense_pf_cp_get_eth_rule_counters("{$anchor}/{$ip}_32");
1784
	if (!empty($result) && is_array($result)) {
1785
		$input_pkts = 0;
1786
		$input_bytes = 0;
1787
		$output_pkts = 0;
1788
		$output_bytes = 0;
1789

    
1790
		foreach ($result as $rule_counters) {
1791
			switch ($rule_counters[0]) {
1792
				case 1: // rule direction 'PF_IN'
1793
					$input_pkts += $rule_counters['input_pkts'];
1794
					$input_bytes += $rule_counters['input_bytes'];
1795
					break;
1796
				case 2: // rule direction 'PF_OUT'
1797
					$output_pkts += $rule_counters['output_pkts'];
1798
					$output_bytes += $rule_counters['output_bytes'];
1799
					break;
1800
				case 0: // rule direction 'PF_INOUT'
1801
					$input_pkts += $rule_counters['input_pkts'];
1802
					$input_bytes += $rule_counters['input_bytes'];
1803
					$output_pkts += $rule_counters['output_pkts'];
1804
					$output_bytes += $rule_counters['output_bytes'];
1805
					break;
1806
				default:
1807
					break;
1808
			}
1809
		}
1810

    
1811
		if ($reverse) {
1812
			$volume['output_pkts'] = $input_pkts;
1813
			$volume['output_bytes'] = $input_bytes;
1814
			$volume['input_pkts'] = $output_pkts;
1815
			$volume['input_bytes'] = $output_bytes;
1816
		} else {
1817
			$volume['output_pkts'] = $output_pkts;
1818
			$volume['output_bytes'] = $output_bytes;
1819
			$volume['input_pkts'] = $input_pkts;
1820
			$volume['input_bytes'] = $input_bytes;
1821
		}
1822
	}
1823

    
1824
	return $volume;
1825
}
1826

    
1827
function portal_ip_from_client_ip($cliip) {
1828
	global $config, $cpzone;
1829

    
1830
	$isipv6 = is_ipaddrv6($cliip);
1831
	$interfaces = explode(",", $config['captiveportal'][$cpzone]['interface']);
1832
	foreach ($interfaces as $cpif) {
1833
		if ($isipv6) {
1834
			$ip = get_interface_ipv6($cpif);
1835
			$sn = get_interface_subnetv6($cpif);
1836
		} else {
1837
			$ip = get_interface_ip($cpif);
1838
			$sn = get_interface_subnet($cpif);
1839
		}
1840
		if (ip_in_subnet($cliip, "{$ip}/{$sn}")) {
1841
			return $ip;
1842
		}
1843
	}
1844

    
1845
	$route = route_get($cliip, 'inet', true);
1846
	if (empty($route)) {
1847
		return false;
1848
	}
1849

    
1850
	$iface = $route[0]['interface-name'];
1851
	if (!empty($iface)) {
1852
		$ip = ($isipv6) ? find_interface_ipv6($iface)
1853
		    : find_interface_ip($iface);
1854
		if (is_ipaddr($ip)) {
1855
			return $ip;
1856
		}
1857
	}
1858

    
1859
	// doesn't match up to any particular interface
1860
	// so let's set the portal IP to what PHP says
1861
	// the server IP issuing the request is.
1862
	// allows same behavior as 1.2.x where IP isn't
1863
	// in the subnet of any CP interface (static routes, etc.)
1864
	// rather than forcing to DNS hostname resolution
1865
	$ip = $_SERVER['SERVER_ADDR'];
1866
	if (is_ipaddr($ip)) {
1867
		return $ip;
1868
	}
1869

    
1870
	return false;
1871
}
1872

    
1873
function portal_hostname_from_client_ip($cliip) {
1874
	global $config, $cpzone;
1875

    
1876
	$cpcfg = config_get_path("captiveportal/{$cpzone}");
1877

    
1878
	if (isset($cpcfg['httpslogin'])) {
1879
		$listenporthttps = $cpcfg['listenporthttps'] ? $cpcfg['listenporthttps'] : ($cpcfg['zoneid'] + 8001);
1880
		$ourhostname = $cpcfg['httpsname'];
1881

    
1882
		if ($listenporthttps != 443) {
1883
			$ourhostname .= ":" . $listenporthttps;
1884
		}
1885
	} else {
1886
		$listenporthttp = $cpcfg['listenporthttp'] ? $cpcfg['listenporthttp'] : ($cpcfg['zoneid'] + 8000);
1887
		$ifip = portal_ip_from_client_ip($cliip);
1888
		if (!$ifip) {
1889
			$ourhostname = "{$config['system']['hostname']}.{$config['system']['domain']}";
1890
		} else {
1891
			$ourhostname = (is_ipaddrv6($ifip)) ? "[{$ifip}]" : "{$ifip}";
1892
		}
1893

    
1894
		if ($listenporthttp != 80) {
1895
			$ourhostname .= ":" . $listenporthttp;
1896
		}
1897
	}
1898

    
1899
	return $ourhostname;
1900
}
1901

    
1902
/* functions move from index.php */
1903

    
1904
function portal_reply_page($redirurl, $type = null, $message = null, $clientmac = null, $clientip = null, $username = null, $password = null, $voucher = null) {
1905
	global $g, $config, $cpzone;
1906

    
1907
	$cpcfg = config_get_path("captiveportal/{$cpzone}");
1908
	$ourhostname = portal_hostname_from_client_ip($clientip);
1909
	$protocol = (isset($cpcfg['httpslogin'])) ? 'https://' : 'http://';
1910
	$portal_url = "{$protocol}{$ourhostname}/index.php?zone={$cpzone}";
1911

    
1912
	/* Get captive portal layout */
1913
	if ($type == "redir") {
1914
		$redirurl = is_URL($redirurl, true) ? $redirurl : $portal_url;
1915
		header("Location: {$redirurl}");
1916
		return;
1917
	} else if ($type == "login") {
1918
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal_{$cpzone}.html");
1919
	} else {
1920
		$htmltext = get_include_contents("{$g['varetc_path']}/captiveportal-{$cpzone}-error.html");
1921
	}
1922

    
1923
	/* substitute the PORTAL_REDIRURL variable */
1924
	if ($cpcfg['preauthurl']) {
1925
		$htmltext = str_replace("\$PORTAL_REDIRURL\$", "{$cpcfg['preauthurl']}", $htmltext);
1926
		$htmltext = str_replace("#PORTAL_REDIRURL#", "{$cpcfg['preauthurl']}", $htmltext);
1927
	}
1928

    
1929
	/* substitute other variables */
1930
	$htmltext = str_replace("\$PORTAL_ACTION\$", $portal_url, $htmltext);
1931
	$htmltext = str_replace("#PORTAL_ACTION#", $portal_url, $htmltext);
1932

    
1933
	$htmltext = str_replace("\$PORTAL_ZONE\$", htmlspecialchars($cpzone), $htmltext);
1934
	$htmltext = str_replace("\$PORTAL_REDIRURL\$", htmlspecialchars($redirurl), $htmltext);
1935
	$htmltext = str_replace("\$PORTAL_MESSAGE\$", htmlspecialchars($message), $htmltext);
1936
	$htmltext = str_replace("\$CLIENT_MAC\$", htmlspecialchars($clientmac), $htmltext);
1937
	$htmltext = str_replace("\$CLIENT_IP\$", htmlspecialchars($clientip), $htmltext);
1938

    
1939
	// Special handling case for captive portal master page so that it can be ran
1940
	// through the PHP interpreter using the include method above.  We convert the
1941
	// $VARIABLE$ case to #VARIABLE# in /etc/inc/captiveportal.inc before writing out.
1942
	$htmltext = str_replace("#PORTAL_ZONE#", htmlspecialchars($cpzone), $htmltext);
1943
	$htmltext = str_replace("#PORTAL_REDIRURL#", htmlspecialchars($redirurl), $htmltext);
1944
	$htmltext = str_replace("#PORTAL_MESSAGE#", htmlspecialchars($message), $htmltext);
1945
	$htmltext = str_replace("#CLIENT_MAC#", htmlspecialchars($clientmac), $htmltext);
1946
	$htmltext = str_replace("#CLIENT_IP#", htmlspecialchars($clientip), $htmltext);
1947
	$htmltext = str_replace("#USERNAME#", htmlspecialchars($username), $htmltext);
1948
	$htmltext = str_replace("#PASSWORD#", htmlspecialchars($password), $htmltext);
1949
	$htmltext = str_replace("#VOUCHER#", htmlspecialchars($voucher), $htmltext);
1950

    
1951
	echo $htmltext;
1952
}
1953

    
1954
function captiveportal_reapply_attributes($cpentry, $attributes) {
1955
	global $config, $cpzone, $g;
1956

    
1957
	if (isset($config['captiveportal'][$cpzone]['peruserbw'])) {
1958
		$dwfaultbw_up = !empty($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0;
1959
		$dwfaultbw_down = !empty($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0;
1960
	} else {
1961
		$dwfaultbw_up = $dwfaultbw_down = 0;
1962
	}
1963
	/* pipe throughputs must always be an integer, enforce that restriction again here. */
1964
	if (isset($config['captiveportal'][$cpzone]['radiusperuserbw'])) {
1965
		$bw_up = round(!empty($attributes['bw_up']) ? intval($attributes['bw_up'])/1000 : $dwfaultbw_up, 0);
1966
		$bw_down = round(!empty($attributes['bw_down']) ? intval($attributes['bw_down'])/1000 : $dwfaultbw_down, 0);
1967
	} else {
1968
		$bw_up = round($dwfaultbw_up,0);
1969
		$bw_down = round($dwfaultbw_down,0);
1970
	}
1971

    
1972
	$bw_up_pipeno = $cpentry[1];
1973
	$bw_down_pipeno = $cpentry[1]+1;
1974

    
1975
	if ($cpentry['bw_up'] !== $bw_up) {
1976
		$_gb = mwexec("/sbin/dnctl pipe {$bw_up_pipeno} config bw {$bw_up}Kbit/s queue 100 buckets 16");
1977
		captiveportal_update_entry($cpentry['sessionid'], $bw_up, 'bw_up');
1978
	}
1979
	if ($cpentry['bw_down'] !== $bw_down) {
1980
		$_gb = mwexec("/sbin/dnctl pipe {$bw_down_pipeno} config bw {$bw_down}Kbit/s queue 100 buckets 16");
1981
		captiveportal_update_entry($cpentry['sessionid'], $bw_down, 'bw_down');
1982
	}
1983
	unset($bw_up_pipeno, $bw_down_pipeno, $bw_up, $bw_down);
1984
}
1985

    
1986
function captiveportal_update_entry($sessionid, $new_value, $field_to_update) {
1987
	global $cpzone;
1988

    
1989
	if (!intval($new_value)) {
1990
		$new_value = "'{$new_value}'";
1991
	}
1992
	captiveportal_write_db("UPDATE captiveportal SET {$field_to_update} = {$new_value} WHERE sessionid = '{$sessionid}'");
1993
}
1994

    
1995
function portal_allow($clientip, $clientmac, $username, $password = null, $redirurl = null,
1996
    $attributes = null, $pipeno = null, $authmethod = null, $context = 'first', $existing_sessionid = null) {
1997
	global $g, $config, $cpzone;
1998

    
1999
	// Ensure we create an array if we are missing attributes
2000
	if (!is_array($attributes)) {
2001
		$attributes = array();
2002
	}
2003

    
2004
	unset($sessionid);
2005

    
2006
	/* Do not allow concurrent login execution. */
2007
	$cpdblck = lock("captiveportaldb{$cpzone}", LOCK_EX);
2008

    
2009
	if ($attributes['voucher']) {
2010
		$remaining_time = $attributes['session_timeout'];
2011
		$authmethod = "voucher"; // Set RADIUS-Attribute to Voucher to prevent ReAuth-Request for Vouchers Bug: #2155
2012
		$context = "voucher";
2013
	}
2014

    
2015
	$writecfg = false;
2016
	/* If both "Add MAC addresses of connected users as pass-through MAC" and "Disable concurrent logins" are checked,
2017
	then we need to check if the user was already authenticated using another MAC Address, and if so remove the previous Pass-Through MAC. */
2018
	if ((isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) && ($config['captiveportal'][$cpzone]['noconcurrentlogins'] == 'last') && ($username != 'unauthenticated') && isset($config['captiveportal'][$cpzone]['passthrumacadd'])) {
2019
		$mac = captiveportal_passthrumac_findbyname($username);
2020
		if (!empty($mac)) {
2021
			foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $idx => $macent) {
2022
				if ($macent['mac'] != $mac['mac']) {
2023
					continue;
2024
				}
2025

    
2026
				captiveportal_passthrumac_delete_entry($macent);
2027
				config_del_path("captiveportal/{$cpzone}/passthrumac/{$idx}");
2028
			}
2029
		}
2030
	}
2031

    
2032
	/* read in client database */
2033
	$query = "WHERE ip = '{$clientip}'";
2034
	$tmpusername = SQLite3::escapeString(strtolower($username));
2035
	if (isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) {
2036
		$query .= " OR (username != 'unauthenticated' AND lower(username) = '{$tmpusername}')";
2037
	}
2038
	$cpdb = captiveportal_read_db($query);
2039

    
2040
	/* Snapshot the timestamp */
2041
	$allow_time = time();
2042

    
2043
	if ($existing_sessionid !== null) {
2044
		// If we received this connection through XMLRPC sync :
2045
		// we fetch allow_time from the info given by the other node
2046
		$allow_time = $attributes['allow_time'];
2047
	}
2048
	$unsetindexes = array();
2049

    
2050
	foreach ($cpdb as $cpentry) {
2051
		/* on the same ip */
2052
		if ($cpentry[2] == $clientip) {
2053
			if (isset($config['captiveportal'][$cpzone]['nomacfilter']) || $cpentry[3] == $clientmac) {
2054
				captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "CONCURRENT LOGIN - REUSING OLD SESSION");
2055
			} else {
2056
				captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "CONCURRENT LOGIN - REUSING IP {$cpentry[2]} WITH DIFFERENT MAC ADDRESS {$cpentry[3]}");
2057
			}
2058
			$sessionid = $cpentry[5];
2059
			break;
2060
		} elseif (($attributes['voucher']) && ($username != 'unauthenticated') && ($cpentry[4] == $username)) {
2061
			// user logged in with an active voucher. Check for how long and calculate
2062
			// how much time we can give him (voucher credit - used time)
2063
			$remaining_time = $cpentry[0] + $cpentry[7] - $allow_time;
2064
			if ($remaining_time < 0) { // just in case.
2065
				$remaining_time = 0;
2066
			}
2067

    
2068
			/* This user was already logged in so we disconnect the old one, or 
2069
			keep the old one, refusing the new login, or
2070
			allow the login */
2071

    
2072
			if (!isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) {
2073
				captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "config['captiveportal'][$cpzone]['noconcurrentlogins'] 2 does not exists = NOT set");		
2074
			} else {
2075
				captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "config['captiveportal'][$cpzone]['noconcurrentlogins'] 2 exists = set");		
2076
			}
2077

    
2078
			if ($config['captiveportal'][$cpzone]['noconcurrentlogins'] == "last") {
2079
				captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "Found last");
2080
			} else {
2081
				captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "Found NOT last");
2082
			}
2083
				
2084
			if (!isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) {
2085
				/* 'noconcurrentlogins' not set : accept login 'username' creating multiple sessions. */
2086
				captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "config['captiveportal'][$cpzone]['noconcurrentlogins'] 3 does not exists = NOT set");
2087
				captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "CONCURRENT LOGIN - NOT TERMINATING EXISTING SESSION(S)");			
2088
			} elseif ($config['captiveportal'][$cpzone]['noconcurrentlogins'] == "last") {
2089
				/* Classic situation : accept the new login, disconnect the old - present - connection */
2090
				if (isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) {
2091
					captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "config['captiveportal'][$cpzone]['noconcurrentlogins'] 4 exists = set");		
2092
				} else {
2093
					captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "config['captiveportal'][$cpzone]['noconcurrentlogins'] 4 does not exists = NOT set");		
2094
				}
2095
				
2096
				captiveportal_disconnect($cpentry, 13);
2097
				captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "CONCURRENT LOGIN - TERMINATING OLD SESSION");
2098
				$unsetindexes[] = $cpentry[5];
2099
				break;
2100
			} else {
2101
				/* Implicit 'first' : refuse the new login - 'username' is already logged in */
2102
				captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "CONCURRENT VOUCHER LOGIN - NOT ALLOWED KEEPING OLD SESSION ");
2103
				unlock($cpdblck);
2104
				return 2;
2105
			}
2106
		} elseif ((isset($config['captiveportal'][$cpzone]['noconcurrentlogins'])) && ($username != 'unauthenticated')) {
2107
			if ($config['captiveportal'][$cpzone]['noconcurrentlogins'] == "last") {
2108
				/* on the same username */
2109
				if (strcasecmp($cpentry[4], $username) == 0) {
2110
					/* This user was already logged in so we disconnect the old one */
2111
					captiveportal_disconnect($cpentry, 13);
2112
					captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "CONCURRENT USER LOGIN - TERMINATING OLD SESSION");
2113
					$unsetindexes[] = $cpentry[5];
2114
					break;
2115
				}
2116
			} else {
2117
				/* Implicit 'first' : refuse the new login - 'username' is already logged in */
2118
				captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "CONCURRENT USER LOGIN - NOT ALLOWED KEEPING OLD SESSION ");
2119
				unlock($cpdblck);
2120
				return 2;				
2121
			}
2122
		}
2123
	}
2124
	unset($cpdb);
2125

    
2126
	if (!empty($unsetindexes)) {
2127
		captiveportal_remove_entries($unsetindexes);
2128
	}
2129

    
2130
	if ($attributes['voucher'] && $remaining_time <= 0) {
2131
		return 0;       // voucher already used and no time left
2132
	}
2133

    
2134
	if (!isset($sessionid)) {
2135
		if ($existing_sessionid != null) { // existing_sessionid should only be set during XMLRPC sync
2136
			$sessionid = $existing_sessionid;
2137
		} else {
2138
			/* generate unique session ID */
2139
			$tod = gettimeofday();
2140
			$sessionid = substr(md5(mt_rand() . $tod['sec'] . $tod['usec'] . $clientip . $clientmac), 0, 16);
2141
		}
2142

    
2143
		if (isset($config['captiveportal'][$cpzone]['peruserbw'])) {
2144
			$dwfaultbw_up = !empty($config['captiveportal'][$cpzone]['bwdefaultup']) ? $config['captiveportal'][$cpzone]['bwdefaultup'] : 0;
2145
			$dwfaultbw_down = !empty($config['captiveportal'][$cpzone]['bwdefaultdn']) ? $config['captiveportal'][$cpzone]['bwdefaultdn'] : 0;
2146
		} else {
2147
			$dwfaultbw_up = $dwfaultbw_down = 0;
2148
		}
2149
		/* pipe throughputs must always be an integer, enforce that restriction again here. */
2150
		if (isset($config['captiveportal'][$cpzone]['radiusperuserbw'])) {
2151
			$bw_up = round(!empty($attributes['bw_up']) ? intval($attributes['bw_up'])/1000 : $dwfaultbw_up, 0);
2152
			$bw_down = round(!empty($attributes['bw_down']) ? intval($attributes['bw_down'])/1000 : $dwfaultbw_down, 0);
2153
		} else {
2154
			$bw_up = round($dwfaultbw_up,0);
2155
			$bw_down = round($dwfaultbw_down,0);
2156
		}
2157

    
2158
		$mac = array();
2159
		$mac['action'] = 'pass';
2160
		$mac['ip'] = $clientip;
2161
		$mac['username'] = $username;
2162
		if (!empty($bw_up)) {
2163
			$mac['bw_up'] = $bw_up;
2164
		}
2165
		if (!empty($bw_down)) {
2166
			$mac['bw_down'] = $bw_down;
2167
		}
2168
		if (isset($config['captiveportal'][$cpzone]['passthrumacadd'])) {
2169
			$mac['mac'] = $clientmac;
2170
			if ($attributes['voucher']) {
2171
				$mac['logintype'] = "voucher";
2172
			}
2173
			if ($username == "unauthenticated") {
2174
				$mac['descr'] = "Auto-added";
2175
			} else if ($authmethod == "voucher") {
2176
				$mac['descr'] = "Auto-added for voucher {$username}";
2177
			} else {
2178
				$mac['descr'] = "Auto-added for user {$username}";
2179
			}
2180
			if (!is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
2181
				$config['captiveportal'][$cpzone]['passthrumac'] = array();
2182
			}
2183
			//check for mac duplicates before adding it to config.
2184
			$mac_duplicate = false;
2185
			foreach($config['captiveportal'][$cpzone]['passthrumac'] as $mac_check){
2186
				if($mac_check['mac'] == $mac['mac']){
2187
					$mac_duplicate = true;
2188
				}
2189
			}
2190
			if(!$mac_duplicate){
2191
				$config['captiveportal'][$cpzone]['passthrumac'][] = $mac;
2192
			}
2193
			unlock($cpdblck);
2194
			captiveportal_ether_configure_entry($mac, 'passthrumac', true);
2195
			$writecfg = true;
2196
		} else {
2197
			/* See if a pipeno is passed, if not start sessions because this means there isn't one atm */
2198
			if (is_null($pipeno)) {
2199
				$pipeno = captiveportal_get_next_dn_ruleno('auth');
2200
			}
2201
			/* if the pool is empty, return appropriate message and exit */
2202
			if (is_null($pipeno)) {
2203
				captiveportal_syslog("Zone: {$cpzone} - WARNING!  Captive portal has reached maximum login capacity");
2204
				unlock($cpdblck);
2205
				return false;
2206
			}
2207

    
2208
			$mac['pipeno'] = $pipeno;
2209
			$mac['ip'] = $clientip;
2210
			if (!isset($config['captiveportal'][$cpzone]['nomacfilter'])) {
2211
				$mac['mac'] = $clientmac;
2212
			}
2213
			captiveportal_ether_configure_entry($mac, 'auth', true);
2214

    
2215
			if ($attributes['voucher']) {
2216
				$attributes['session_timeout'] = $remaining_time;
2217
			}
2218

    
2219
			/* handle empty attributes */
2220
			$session_timeout = (!empty($attributes['session_timeout'])) ? $attributes['session_timeout'] : 'NULL';
2221
			$idle_timeout = (!empty($attributes['idle_timeout'])) ? $attributes['idle_timeout'] : 'NULL';
2222
			$session_terminate_time = (!empty($attributes['session_terminate_time'])) ? $attributes['session_terminate_time'] : 'NULL';
2223
			$interim_interval = (!empty($attributes['interim_interval'])) ? $attributes['interim_interval'] : 'NULL';
2224
			$traffic_quota = (!empty($attributes['maxbytes'])) ? $attributes['maxbytes'] : 'NULL';
2225

    
2226
			/* escape username */
2227
			$safe_username = SQLite3::escapeString($username);
2228

    
2229
			/* encode password in Base64 just in case it contains commas */
2230
			$bpassword = (isset($config['captiveportal'][$cpzone]['reauthenticate'])) ? base64_encode($password) : '';
2231
			$insertquery = "INSERT INTO captiveportal (allow_time, pipeno, ip, mac, username, sessionid, bpassword, session_timeout, idle_timeout, session_terminate_time, interim_interval, traffic_quota, bw_up, bw_down, authmethod, context) ";
2232
			$insertquery .= "VALUES ({$allow_time}, {$pipeno}, '{$clientip}', '{$clientmac}', '{$safe_username}', '{$sessionid}', '{$bpassword}', ";
2233
			$insertquery .= "{$session_timeout}, {$idle_timeout}, {$session_terminate_time}, {$interim_interval}, {$traffic_quota}, {$bw_up}, {$bw_down}, '{$authmethod}', '{$context}')";
2234

    
2235
			/* store information to database */
2236
			captiveportal_write_db($insertquery);
2237
			unlock($cpdblck);
2238
			unset($insertquery, $bpassword);
2239

    
2240
			$radacct = isset($config['captiveportal'][$cpzone]['radacct_enable']) ? true : false;
2241
			if ($authmethod === 'radius' && $radacct) {
2242
				captiveportal_send_server_accounting('start',
2243
					$pipeno, // ruleno
2244
					$username, // username
2245
					$clientip, // clientip
2246
					$clientmac, // clientmac
2247
					$sessionid, // sessionid
2248
					time());  // start time
2249
			}
2250
			if (captiveportal_xmlrpc_sync_get_details($syncip, $syncport, $syncuser, $syncpass, isset($existing_sessionid))) {
2251
				// $existing_sessionid prevent carp loop : only forward
2252
				// the connection to the other node if we generated the sessionid by ourselves
2253
				$rpc_client = new pfsense_xmlrpc_client();
2254
				$rpc_client->setConnectionData($syncip, $syncport, $syncuser, $syncpass);
2255
				$rpc_client->set_noticefile("CaptivePortalUserSync");
2256
				$arguments = array(
2257
					'clientip' => $clientip,
2258
					'clientmac' => $clientmac,
2259
					'username' => $username,
2260
					'password' => $password,
2261
					'attributes' => $attributes,
2262
					'allow_time' => $allow_time,
2263
					'authmethod' => $authmethod,
2264
					'context' => $context,
2265
					'sessionid' => $sessionid
2266
				);
2267

    
2268
				$rpc_client->xmlrpc_method('captive_portal_sync',
2269
					array(
2270
						'op' => 'connect_user',
2271
						'zone' => $cpzone,
2272
						'user' => base64_encode(serialize($arguments))
2273
					)
2274
				);
2275
			}
2276
		}
2277
	} else {
2278
		/* NOTE: #3062-11 If the pipeno has been allocated free it to not DoS the CP */
2279
		if (!is_null($pipeno)) {
2280
			captiveportal_free_dn_rulenos(array($pipeno, $pipeno+1));
2281
		}
2282

    
2283
		unlock($cpdblck);
2284
	}
2285

    
2286
	if ($writecfg == true) {
2287
		write_config(gettext("Captive Portal allowed users configuration changed"));
2288
	}
2289

    
2290
	if ($existing_sessionid !== null) {
2291
		if (!empty($sessionid)) {
2292
			return $sessionid;
2293
		} else {
2294
			return false;
2295
		}
2296
	}
2297
	/* redirect user to desired destination */
2298
	if (is_URL($attributes['url_redirection'], true)) {
2299
		$my_redirurl = $attributes['url_redirection'];
2300
	} else if (is_URL($config['captiveportal'][$cpzone]['redirurl'], true)) {
2301
		$my_redirurl = config_get_path("captiveportal/{$cpzone}/redirurl");
2302
	} else if (is_URL($redirurl, true)) {
2303
		$my_redirurl = $redirurl;
2304
	}
2305

    
2306
	if (isset($config['captiveportal'][$cpzone]['logoutwin_enable']) && !isset($config['captiveportal'][$cpzone]['passthrumacadd'])) {
2307
		$ourhostname = portal_hostname_from_client_ip($clientip);
2308
		$protocol = (isset($config['captiveportal'][$cpzone]['httpslogin'])) ? 'https://' : 'http://';
2309
		$logouturl = "{$protocol}{$ourhostname}/";
2310

    
2311
		if (isset($attributes['reply_message'])) {
2312
			$message = $attributes['reply_message'];
2313
		} else {
2314
			$message = 0;
2315
		}
2316

    
2317
		include_once("{$g['varetc_path']}/captiveportal-{$cpzone}-logout.html");
2318

    
2319
	} else {
2320
		portal_reply_page($my_redirurl, "redir", "Just redirect the user.");
2321
	}
2322

    
2323
	return $sessionid;
2324
}
2325

    
2326

    
2327
/*
2328
 * Used for when pass-through credits are enabled.
2329
 * Returns true when there was at least one free login to deduct for the MAC.
2330
 * Expired entries are removed as they are seen.
2331
 * Active entries are updated according to the configuration.
2332
 */
2333
function portal_consume_passthrough_credit($clientmac) {
2334
	global $config, $cpzone;
2335

    
2336
	if (!empty($config['captiveportal'][$cpzone]['freelogins_count']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_count'])) {
2337
		$freeloginscount = config_get_path("captiveportal/{$cpzone}/freelogins_count");
2338
	} else {
2339
		return false;
2340
	}
2341

    
2342
	if (!empty($config['captiveportal'][$cpzone]['freelogins_resettimeout']) && is_numeric($config['captiveportal'][$cpzone]['freelogins_resettimeout'])) {
2343
		$resettimeout = config_get_path("captiveportal/{$cpzone}/freelogins_resettimeout");
2344
	} else {
2345
		return false;
2346
	}
2347

    
2348
	if ($freeloginscount < 1 || $resettimeout <= 0 || !$clientmac) {
2349
		return false;
2350
	}
2351

    
2352
	$updatetimeouts = isset($config['captiveportal'][$cpzone]['freelogins_updatetimeouts']);
2353

    
2354
	/*
2355
	 * Read database of used MACs.  Lines are a comma-separated list
2356
	 * of the time, MAC, then the count of pass-through credits remaining.
2357
	 */
2358
	$usedmacs = captiveportal_read_usedmacs_db();
2359

    
2360
	$currenttime = time();
2361
	$found = false;
2362
	foreach ($usedmacs as $key => $usedmac) {
2363
		$usedmac = explode(",", $usedmac);
2364

    
2365
		if ($usedmac[1] == $clientmac) {
2366
			if ($usedmac[0] + ($resettimeout * 3600) > $currenttime) {
2367
				if ($usedmac[2] < 1) {
2368
					if ($updatetimeouts) {
2369
						$usedmac[0] = $currenttime;
2370
						unset($usedmacs[$key]);
2371
						$usedmacs[] = implode(",", $usedmac);
2372
						captiveportal_write_usedmacs_db($usedmacs);
2373
						xmlrpc_sync_usedmacs($usedmacs);
2374
					}
2375

    
2376
					return false;
2377
				} else {
2378
					$usedmac[2] -= 1;
2379
					$usedmacs[$key] = implode(",", $usedmac);
2380
				}
2381

    
2382
				$found = true;
2383
			} else {
2384
				unset($usedmacs[$key]);
2385
			}
2386

    
2387
			break;
2388
		} else if ($usedmac[0] + ($resettimeout * 3600) <= $currenttime) {
2389
			unset($usedmacs[$key]);
2390
		}
2391
	}
2392

    
2393
	if (!$found) {
2394
		$usedmac = array($currenttime, $clientmac, $freeloginscount - 1);
2395
		$usedmacs[] = implode(",", $usedmac);
2396
	}
2397

    
2398
	captiveportal_write_usedmacs_db($usedmacs);
2399
	xmlrpc_sync_usedmacs($usedmacs);
2400
	return true;
2401
}
2402

    
2403
function xmlrpc_sync_usedmacs($usedmacs) { 
2404
	global $config, $cpzone;
2405

    
2406
	// XMLRPC Call over to the other node
2407
	if (captiveportal_xmlrpc_sync_get_details($syncip, $syncport,
2408
	    $syncuser, $syncpass, $carp_loop)) {
2409
		$rpc_client = new pfsense_xmlrpc_client();
2410
		$rpc_client->setConnectionData($syncip, $syncport, $syncuser, $syncpass);
2411
		$rpc_client->set_noticefile("CaptivePortalUsedmacsSync");
2412
		$arguments = array(
2413
			'usedmacs' => $usedmacs
2414
		);
2415

    
2416
		$rpc_client->xmlrpc_method('captive_portal_sync',
2417
			array(
2418
				'op' => 'write_usedmacs',
2419
				'zone' => $cpzone,
2420
				'arguments' => base64_encode(serialize($arguments))
2421
			)
2422
		);
2423
	}
2424
}
2425

    
2426
function captiveportal_read_usedmacs_db() {
2427
	global $g, $cpzone;
2428

    
2429
	$cpumaclck = lock("captiveusedmacs{$cpzone}");
2430
	if (file_exists("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db")) {
2431
		$usedmacs = file("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
2432
		if (!$usedmacs) {
2433
			$usedmacs = array();
2434
		}
2435
	} else {
2436
		$usedmacs = array();
2437
	}
2438

    
2439
	unlock($cpumaclck);
2440
	return $usedmacs;
2441
}
2442

    
2443
function captiveportal_write_usedmacs_db($usedmacs) {
2444
	global $g, $cpzone;
2445

    
2446
	$cpumaclck = lock("captiveusedmacs{$cpzone}", LOCK_EX);
2447
	@file_put_contents("{$g['vardb_path']}/captiveportal_usedmacs_{$cpzone}.db", implode("\n", $usedmacs));
2448
	unlock($cpumaclck);
2449
}
2450

    
2451
function captiveportal_blocked_mac($mac) {
2452
	global $config, $g, $cpzone;
2453

    
2454
	if (empty($mac) || !is_macaddr($mac)) {
2455
		return false;
2456
	}
2457

    
2458
	if (!is_array($config['captiveportal'][$cpzone]['passthrumac'])) {
2459
		return false;
2460
	}
2461

    
2462
	foreach ($config['captiveportal'][$cpzone]['passthrumac'] as $passthrumac) {
2463
		if (($passthrumac['action'] == 'block') &&
2464
		    ($passthrumac['mac'] == strtolower($mac))) {
2465
			return true;
2466
		}
2467
	}
2468

    
2469
	return false;
2470

    
2471
}
2472

    
2473
/* Captiveportal Radius Accounting */
2474

    
2475
function gigawords($bytes) {
2476

    
2477
	/*
2478
	 * RFC2866 Specifies a 32bit unsigned integer, which is a max of 4294967295
2479
	 * Currently there is a fault in the PECL radius_put_int function which can handle only 32bit signed integer.
2480
	 */
2481

    
2482
	// We use BCMath functions since normal integers don't work with so large numbers
2483
	$gigawords = bcdiv( bcsub( $bytes, remainder($bytes) ) , GIGAWORDS_RIGHT_OPERAND) ;
2484

    
2485
	// We need to manually set this to a zero instead of NULL for put_int() safety
2486
	if (is_null($gigawords)) {
2487
		$gigawords = 0;
2488
	}
2489

    
2490
	return $gigawords;
2491
}
2492

    
2493
function remainder($bytes) {
2494
	// Calculate the bytes we are going to send to the radius
2495
	$bytes = bcmod($bytes, GIGAWORDS_RIGHT_OPERAND);
2496

    
2497
	if (is_null($bytes)) {
2498
		$bytes = 0;
2499
	}
2500

    
2501
    return $bytes;
2502
}
2503

    
2504
function captiveportal_send_server_accounting($type = 'on', $ruleno = null, $username = null, $clientip = null, $clientmac = null, $sessionid = null, $start_time = null, $stop_time = null, $term_cause = null) {
2505
	global $cpzone, $config;
2506

    
2507
	$cpcfg = config_get_path("captiveportal/{$cpzone}");
2508
	$acctcfg = auth_get_authserver($cpcfg['radacct_server']);
2509

    
2510
	if (!isset($cpcfg['radacct_enable']) || empty($acctcfg) ||
2511
	    captiveportal_ha_is_node_in_backup_mode($cpzone)) {
2512
		return null;
2513
	}
2514

    
2515
	if ($type === 'on') {
2516
		$racct = new Auth_RADIUS_Acct_On;
2517
	} elseif ($type === 'off') {
2518
		$racct = new Auth_RADIUS_Acct_Off;
2519
	} elseif ($type === 'start') {
2520
		$racct = new Auth_RADIUS_Acct_Start;
2521
		if (!is_int($start_time)) {
2522
			$start_time = time();
2523
		}
2524
	} elseif ($type === 'stop') {
2525
		$racct = new Auth_RADIUS_Acct_Stop;
2526
		if (!is_int($stop_time)) {
2527
			$stop_time = time();
2528
		}
2529
	} elseif ($type === 'update') {
2530
        $racct = new Auth_RADIUS_Acct_Update;
2531
		if (!is_int($stop_time)) {
2532
			$stop_time = time(); // "top time" here will be used only for calculating session time.
2533
		}
2534
	} else {
2535
		return null;
2536
	}
2537

    
2538
	$racct->addServer($acctcfg['host'], $acctcfg['radius_acct_port'],
2539
		$acctcfg['radius_secret'], $acctcfg['radius_timeout']);
2540

    
2541
	$racct->authentic = RADIUS_AUTH_RADIUS;
2542
	if ($cpcfg['auth_method'] === 'radmac' && $username === "unauthenticated" && !empty($clientmac)) {
2543
		$racct->username = mac_format($clientmac);
2544
	} elseif (!empty($username)) {
2545
		$racct->username = $username;
2546
	}
2547

    
2548
	if (PEAR::isError($racct->start())) {
2549
		captiveportal_syslog('RADIUS ACCOUNTING FAILED : '.$racct->getError());
2550
		$racct->close();
2551
		return null;
2552
	}
2553

    
2554
	$nasip = nasip_fallback($acctcfg['radius_nasip_attribute']);
2555
	$nasmac = get_interface_mac(find_ip_interface($nasip));
2556
	$racct->putAttribute(RADIUS_NAS_IP_ADDRESS, $nasip, "addr");
2557

    
2558
	$racct->putAttribute(RADIUS_NAS_IDENTIFIER, empty($cpcfg["radiusnasid"]) ? "CaptivePortal-{$cpzone}" : $cpcfg["radiusnasid"] );
2559

    
2560
	if (is_int($ruleno)) {
2561
		$racct->putAttribute(RADIUS_NAS_PORT_TYPE, RADIUS_ETHERNET);
2562
		$racct->putAttribute(RADIUS_NAS_PORT, intval($ruleno), 'integer');
2563
	}
2564

    
2565
	if (!empty($sessionid)) {
2566
		$racct->putAttribute(RADIUS_ACCT_SESSION_ID, $sessionid);
2567
	}
2568

    
2569
	if (!empty($clientip) && is_ipaddr($clientip)) {
2570
		$racct->putAttribute(RADIUS_FRAMED_IP_ADDRESS, $clientip, "addr");
2571
	}
2572
	if (!empty($clientmac)) {
2573
		$racct->putAttribute(RADIUS_CALLING_STATION_ID, mac_format($clientmac));
2574
	}
2575
	if (!empty($nasmac)) {
2576
		$racct->putAttribute(RADIUS_CALLED_STATION_ID, mac_format($nasmac).':'.gethostname());
2577
	}
2578

    
2579
	// Accounting request Stop and Update : send the current data volume
2580
	if (($type === 'stop' || $type === 'update') && is_int($start_time)) {
2581
		$volume = getVolume($clientip);
2582
		$session_time = $stop_time - $start_time;
2583
		$volume['input_bytes_radius'] = remainder($volume['input_bytes']);
2584
		$volume['input_gigawords'] = gigawords($volume['input_bytes']);
2585
		$volume['output_bytes_radius'] = remainder($volume['output_bytes']);
2586
		$volume['output_gigawords'] = gigawords($volume['output_bytes']);
2587

    
2588
		// Volume stuff: Ingress
2589
		$racct->putAttribute(RADIUS_ACCT_INPUT_PACKETS, intval($volume['input_pkts']), "integer");
2590
		$racct->putAttribute(RADIUS_ACCT_INPUT_OCTETS, intval($volume['input_bytes_radius']), "integer");
2591
		// Volume stuff: Outgress
2592
		$racct->putAttribute(RADIUS_ACCT_OUTPUT_PACKETS, intval($volume['output_pkts']), "integer");
2593
		$racct->putAttribute(RADIUS_ACCT_OUTPUT_OCTETS, intval($volume['output_bytes_radius']), "integer");
2594
		$racct->putAttribute(RADIUS_ACCT_SESSION_TIME, intval($session_time), "integer");
2595

    
2596
		$racct->putAttribute(CUSTOM_RADIUS_ACCT_OUTPUT_GIGAWORDS, intval($volume['output_gigawords']), "integer");
2597
		$racct->putAttribute(CUSTOM_RADIUS_ACCT_INPUT_GIGAWORDS, intval($volume['input_gigawords']), "integer");
2598
		// Set session_time
2599
		$racct->session_time = $session_time;
2600
	}
2601

    
2602
	if ($type === 'stop') {
2603
		if (empty($term_cause)) {
2604
			$term_cause = 1;
2605
		}
2606
		$racct->putAttribute(RADIUS_ACCT_TERMINATE_CAUSE, $term_cause);
2607
	}
2608

    
2609
	// Send request
2610
	$result = $racct->send();
2611

    
2612
	if (PEAR::isError($result)) {
2613
		 captiveportal_syslog('RADIUS ACCOUNTING FAILED : '.$racct->getError());
2614
		 $result = null;
2615
	} elseif ($result !== true) {
2616
		$result = false;
2617
	}
2618

    
2619
	$racct->close();
2620
	return $result;
2621
}
2622

    
2623
function captiveportal_isip_logged($clientip) {
2624
	global $g, $cpzone;
2625

    
2626
	/* read in client database */
2627
	$query = "WHERE ip = '{$clientip}'";
2628
	$cpdb = captiveportal_read_db($query);
2629
	foreach ($cpdb as $cpentry) {
2630
		return $cpentry;
2631
	}
2632
}
2633

    
2634
function captiveportal_allowedhostname_cleanup() {
2635
	global $g, $config, $cpzone;
2636

    
2637
	$cpzoneprefix = CPPREFIX . $config['captiveportal'][$cpzone]['zoneid'];
2638

    
2639
	init_config_arr(array('captiveportal', $cpzone, 'allowedhostname'));
2640
	foreach ($config['captiveportal'][$cpzone]['allowedhostname'] as $id => $hostnameent) {
2641
		$pipes = pfSense_pf_cp_get_eth_pipes("{$cpzoneprefix}_allowedhosts/hostname_{$id}");
2642
		pfSense_pf_cp_flush("{$cpzoneprefix}_allowedhosts/hostname_{$id}", "ether");
2643
		if (!empty($pipes)) {
2644
			captiveportal_pipes_delete($pipes);
2645
		}
2646
	}
2647
}
2648

    
2649
function filter_captiveportal_aliases() {
2650
	/* return all aliases used in captive portal zones, 
2651
	 * to prevent it from deletion in filter_configure_sync() as unused aliases */
2652
	global $g, $config;
2653

    
2654
	$aliasesnames = array();
2655

    
2656
	init_config_arr(array('captiveportal'));
2657
	foreach ($config['captiveportal'] as $cpzone => $cpcfg) {
2658
		if (isset($cpcfg['enable'])) {
2659
			$cpzoneprefix = CPPREFIX . $config['captiveportal'][$cpzone]['zoneid'];
2660
			$aliasesnames[] = $cpzoneprefix . '_cpips';
2661
			init_config_arr(array('captiveportal', $cpzone, 'allowedhostname'));
2662
			foreach ($config['captiveportal'][$cpzone]['allowedhostname'] as $id => $hostnameent) {
2663
				$aliasesnames[] = $cpzoneprefix . '_hostname_' . $id;
2664
			}
2665
		}
2666
	}
2667

    
2668
	return $aliasesnames;
2669
}
2670

    
2671
function filter_captiveportal_tables() {
2672
	/* return pf rules which defines tables used in captive portal zones */
2673
	global $config, $FilterIflist;
2674

    
2675
	$rules = '';
2676
	init_config_arr(array('captiveportal'));
2677
	foreach ($config['captiveportal'] as $cpzone => $cpcfg) {
2678
		if (!isset($cpcfg['enable'])) {
2679
			continue;
2680
		}
2681

    
2682
		$cpzoneprefix = CPPREFIX . $config['captiveportal'][$cpzone]['zoneid'];
2683
		$cpips = $cpzoneprefix . '_cpips';
2684
		$cpiplist = array();
2685

    
2686
		foreach (explode(",", $cpcfg['interface']) as $cpifgrp) {
2687
			if (isset($FilterIflist[$cpifgrp])) {
2688
				$realif = get_real_interface($cpifgrp);
2689
				if (!empty($realif)) {
2690
					$cpip = get_interface_ip($cpifgrp);
2691
					if (is_ipaddrv4($cpip)) {
2692
						$cpipliststring = $cpip . ' ' . get_interface_vip_ips($cpifgrp);
2693
						$cpiplist = array_filter(array_merge($cpiplist, explode(' ', $cpipliststring)),
2694
												 function ($val) {
2695
													 return (trim($val) != "");
2696
												 });
2697
					}
2698
				}
2699
			}
2700
		}
2701
		if (!empty($cpiplist)) {
2702
			/* captive portal web server IP addresses */
2703
			$rules .= "table <{$cpips}> { " . join(' ', $cpiplist)  . "}\n";
2704
		}
2705
	}
2706

    
2707
	if (!empty($rules)) {
2708
		$rules = "\n# Captive Portal\n" . $rules . "\n";
2709
	}
2710

    
2711
	return $rules;
2712
}
2713

    
2714
function filter_captiveportal_ether() {
2715
	global $g, $config;
2716

    
2717
	$rules = '';
2718
	init_config_arr(array('captiveportal'));
2719
	foreach ($config['captiveportal'] as $cpzone => $cpcfg) {
2720
		if (!isset($cpcfg['enable'])) {
2721
			continue;
2722
		}
2723

    
2724
		$cpzoneprefix = CPPREFIX . $config['captiveportal'][$cpzone]['zoneid'];
2725
		$rdrtag = $cpzoneprefix . '_rdr';
2726
		$interfaces = captiveportal_zone_interfaces($cpcfg);
2727

    
2728
		if (!empty($interfaces)) {
2729
			/* set 'rdr' tag for further captive portal web portal redirection */
2730
			$rules .= "ether pass on { {$interfaces} } tag \"{$rdrtag}\"\n";
2731
			/* anchor to set the PASS tag for authenticated clients */
2732
			$rules .= "ether anchor \"{$cpzoneprefix}_auth/*\" on { {$interfaces} }\n";
2733
			/* anchor for Services / Captive Portal / CPZONE / MACs */
2734
			$rules .= "ether anchor \"{$cpzoneprefix}_passthrumac/*\" on { {$interfaces} }\n";
2735
			/* anchor to set the PASSTHRU tag for Allowed IP/Hostnames */
2736
			$rules .= "ether anchor \"{$cpzoneprefix}_allowedhosts/*\" on { {$interfaces} }\n";
2737
		}
2738
	}
2739

    
2740
	if (!empty($rules)) {
2741
		$rules = "\n# Captive Portal\n" . $rules . "\n";
2742
	}
2743

    
2744
	return $rules;
2745
}
2746

    
2747
function filter_captiveportal_rdr() {
2748
	global $g, $config, $FilterIflist;
2749

    
2750
	$rules = '';
2751
	foreach ($config['captiveportal'] as $cpzone => $cpcfg) {
2752
		if (!isset($cpcfg['enable'])) {
2753
			continue;
2754
		}
2755

    
2756
		$cpzoneprefix = CPPREFIX . $config['captiveportal'][$cpzone]['zoneid'];
2757
		$rdrtag = $cpzoneprefix . '_rdr';
2758
		$cpips = $cpzoneprefix . '_cpips';
2759
		$rdr_ports = captiveportal_zone_portalports($cpcfg);
2760
		foreach (explode(",", $cpcfg['interface']) as $cpifgrp) {
2761
			if (isset($FilterIflist[$cpifgrp])) {
2762
				$realif = get_real_interface($cpifgrp);
2763
				if (!empty($realif)) {
2764
					$cpip = get_interface_ip($cpifgrp);
2765
					if (is_ipaddrv4($cpip)) {
2766
						foreach ($rdr_ports as list($portalias, $cprdrport)) {
2767
							$rules .= "rdr on {$realif} inet proto tcp from any to ! <{$cpips}> port {$cprdrport} tagged {$rdrtag} -> {$cpip} port {$portalias}\n";
2768
						}
2769
					}
2770
				}
2771
			}
2772
		}
2773
	}
2774

    
2775
	if (!empty($rules)) {
2776
		$rules = "\n# Captive Portal\n" . $rules . "\n";
2777
	}
2778

    
2779
	return $rules;
2780
}
2781

    
2782
function filter_captiveportal_pass() {
2783
	global $g, $config, $FilterIflist;
2784

    
2785
	$captiveportal_increment = 'filter_captiveportal_tracker';
2786

    
2787
	$rules = '';
2788
	foreach ($config['captiveportal'] as $cpzone => $cpcfg) {
2789
		if (!isset($cpcfg['enable'])) {
2790
			continue;
2791
		}
2792

    
2793
		$cpzoneprefix = CPPREFIX . $config['captiveportal'][$cpzone]['zoneid'];
2794
		$cpips = $cpzoneprefix . '_cpips';
2795
		$authtag = $cpzoneprefix . '_auth';
2796
		$rdr_ports = captiveportal_zone_portalports($cpcfg);
2797

    
2798
		foreach (explode(",", $cpcfg['interface']) as $cpifgrp) {
2799
			if (!isset($FilterIflist[$cpifgrp])) {
2800
				continue;
2801
			}
2802
			$realif = get_real_interface($cpifgrp);
2803
			if (!empty($realif)) {
2804
				$cpip = get_interface_ip($cpifgrp);
2805
				if (is_ipaddrv4($cpip)) {
2806
					foreach ($rdr_ports as list($portalias, $cprdrport)) {						/* pass non-authenticated clients to captive portal */
2807
						$rules .= "pass in quick on {$realif} proto tcp from any to <{$cpips}> port {$portalias} ridentifier {$captiveportal_increment()} keep state(sloppy)\n";
2808
						/* without this rule captive portal doesn't show login page after manual disconnect */
2809
						$rules .= "pass out quick on {$realif} proto tcp from {$cpip} port {$portalias} to any flags any ridentifier {$captiveportal_increment()} keep state(sloppy)\n";
2810
					}
2811
					/* block non-authenticated clients access to internet */
2812
					$rules .= "block in quick on {$realif} from any to ! <{$cpips}> ! tagged {$authtag} ridentifier {$captiveportal_increment()}\n";
2813
				}
2814
			}
2815
		}
2816
	}
2817

    
2818
	if (!empty($rules)) {
2819
		$rules = "\n# Captive Portal\n" . $rules . "\n";
2820
	}
2821

    
2822
	return $rules;
2823
}
2824

    
2825
function captiveportal_zone_interfaces($cpcfg) {
2826
	/* return a list of captive portal zone interfaces */
2827
	global $FilterIflist;
2828

    
2829
	$interfaces = '';
2830
	foreach (explode(",", $cpcfg['interface']) as $cpifgrp) {
2831
		if (isset($FilterIflist[$cpifgrp])) {
2832
			$realif = get_real_interface($cpifgrp);
2833
			if (!empty($realif) && get_interface_ip($realif)) {
2834
				$interfaces .= $realif . ' '; 
2835
			}
2836
		}
2837
	}
2838
	return $interfaces;
2839
}
2840

    
2841
/*
2842
 * Returns an array of (alias, rdrport) pairs describing ports to be forwarded for the captive portal
2843
 */
2844
function captiveportal_zone_portalports($cpcfg) {
2845
	$rdr_ports = array();
2846
	if (isset($cpcfg['httpslogin']) && !isset($cpcfg['nohttpsforwards'])) {
2847
		$portalias = $cpcfg['listenporthttps'] ? $cpcfg['listenporthttps'] : 8001 + $cpcfg['zoneid'];
2848
		$cprdrport = '443';
2849
		array_push($rdr_ports, array($portalias, $cprdrport));
2850
	}
2851
	$portalias = $cpcfg['listenporthttp'] ? $cpcfg['listenporthttp'] : 8000 + $cpcfg['zoneid'];
2852
	$cprdrport = '80';
2853
	array_push($rdr_ports, array($portalias, $cprdrport));
2854

    
2855
	return $rdr_ports;
2856
}
2857

    
2858
function captiveportal_pipes_delete($pipes) {
2859
	if (!empty($pipes)) {
2860
		foreach ($pipes as $pipe) {
2861
			mwexec("/sbin/dnctl pipe delete {$pipe}");
2862
		}
2863
		captiveportal_free_dn_rulenos($pipes);
2864
	}
2865
}
2866

    
2867
function captiveportal_ether_configure_entry($hostent, $anchor, $user_auth = false) {
2868
	global $config, $g, $cpzone;
2869

    
2870
	if (($hostent['action'] == 'block') && ($anchor == 'passthrumac')) {
2871
		return;
2872
	}
2873

    
2874
	$cpzoneprefix = CPPREFIX . $config['captiveportal'][$cpzone]['zoneid'];
2875
	if ($anchor == 'passthrumac') {
2876
		$tag = $cpzoneprefix . '_auth';
2877
	} else {
2878
		$tag = $cpzoneprefix . '_' . $anchor;
2879
	}
2880

    
2881
	if ($anchor == 'passthrumac') {
2882
		list($pipeup, $pipedown) = captiveportal_pipe_configure($hostent, 'pipe_mac', $user_auth);
2883
		$host = str_replace("/", "_", str_replace(":", "", $hostent['mac']));
2884
		$l3from = '';
2885
		$l3to = '';
2886
		$macfrom = "from {$hostent['mac']}";
2887
		$macto = "to {$hostent['mac']}";
2888
	} else {
2889
		list($pipeup, $pipedown) = captiveportal_pipe_configure($hostent, 'auth', $user_auth);
2890
		$host = $hostent['ip'] . '_32';
2891
		$l3from = "l3 from {$hostent['ip']}";
2892
		$l3to = "l3 to {$hostent['ip']}";
2893
		if (!isset($config['captiveportal'][$cpzone]['nomacfilter'])) { 
2894
			if (!empty($hostent['mac'])) {
2895
				$macfrom = "from {$hostent['mac']}";
2896
				$macto = "to {$hostent['mac']}";
2897
			} else {
2898
				return;
2899
			}
2900
		} else {
2901
			$macfrom = '';
2902
			$macto = '';
2903
		}
2904
	}
2905

    
2906
	$rules = "ether pass in quick {$macfrom} {$l3from} tag {$tag} dnpipe {$pipeup}\n";
2907
	$rules .= "ether pass out quick {$macto} {$l3to} tag {$tag} dnpipe {$pipedown}\n";
2908

    
2909
	captiveportal_load_pfctl("{$cpzoneprefix}_{$anchor}", $host, $rules);
2910
}
2911

    
2912
function captiveportal_pipe_configure($host, $type, $user_auth = true) {
2913
	global $config, $cpzone;
2914

    
2915
	$bwUp = 0;
2916
	if (!empty($host['bw_up'])) {
2917
		$bwUp = $host['bw_up'];
2918
	} elseif ($user_auth &&
2919
		isset($config['captiveportal'][$cpzone]['peruserbw']) &&
2920
	    !empty($config['captiveportal'][$cpzone]['bwdefaultup'])) {
2921
		$bwUp = config_get_path("captiveportal/{$cpzone}/bwdefaultup");
2922
	}
2923
	$bwDown = 0;
2924
	if (!empty($host['bw_down'])) {
2925
		$bwDown = $host['bw_down'];
2926
	} elseif ($user_auth &&
2927
		isset($config['captiveportal'][$cpzone]['peruserbw']) &&
2928
	    !empty($config['captiveportal'][$cpzone]['bwdefaultdn'])) {
2929
		$bwDown = config_get_path("captiveportal/{$cpzone}/bwdefaultdn");
2930
	}
2931

    
2932
	if (isset($host['pipeno']) && !empty($host['pipeno'])) {
2933
		$pipeup = $host['pipeno'];
2934
	} else {
2935
		$pipeup = captiveportal_get_next_dn_ruleno($type);
2936
	}
2937

    
2938
	mwexec("/sbin/dnctl pipe {$pipeup} config bw {$bwUp}Kbit/s queue 100 buckets 16");
2939
	$pipedown = $pipeup + 1;
2940
	mwexec("/sbin/dnctl pipe {$pipedown} config bw {$bwDown}Kbit/s queue 100 buckets 16");
2941

    
2942
	return array($pipeup, $pipedown);
2943
}
2944

    
2945
function captiveportal_allowedip_configure_entry($ipent) {
2946
	global $g, $config, $cpzone;
2947

    
2948
	$cpzoneprefix = CPPREFIX . $config['captiveportal'][$cpzone]['zoneid'];
2949
	$tag = $cpzoneprefix . '_auth';
2950

    
2951
	if (empty($ipent['sn'])) {
2952
		$ipent['sn'] = '32';
2953
	}
2954

    
2955
	$host = $ipent['ip'] . '_' . $ipent['sn'];
2956
	list($pipeup, $pipedown) = captiveportal_pipe_configure($ipent, 'allowed', false);
2957

    
2958
	$rules = '';
2959
	if (($ipent['dir'] == 'to') || ($ipent['dir'] == 'both')) {
2960
		$rules = "ether pass in quick l3 to {$ipent['ip']}/{$ipent['sn']} tag {$tag} dnpipe {$pipeup}\n";
2961
	}
2962
	if (($ipent['dir'] == 'from') || ($ipent['dir'] == 'both')) {
2963
		$rules .= "ether pass in quick l3 from {$ipent['ip']}/{$ipent['sn']} tag {$tag} dnpipe {$pipedown}\n";
2964
	}
2965

    
2966
	captiveportal_load_pfctl("{$cpzoneprefix}_allowedhosts", $host, $rules);
2967
}
2968

    
2969
function captiveportal_allowedhostname_configure_entry($ipent, $hostnameid = 1) {
2970
	global $config, $cpzone;
2971

    
2972
	if (!isset($ipent['hostname'])) {
2973
		return;
2974
	}
2975

    
2976
	$cpzoneprefix = CPPREFIX . $config['captiveportal'][$cpzone]['zoneid'];
2977
	$tag = $cpzoneprefix . '_auth';
2978
	$table = $cpzoneprefix . '_hostname_' . $hostnameid;
2979
	$host = 'hostname_' . $hostnameid;
2980
	list($pipeup, $pipedown) = captiveportal_pipe_configure($ipent, 'allowed', false);
2981

    
2982
	$rules = "table <{$table}> persist\n";
2983
	if (($ipent['dir'] == 'to') || ($ipent['dir'] == 'both')) {
2984
		$rules .= "ether pass in quick l3 to <{$table}> tag {$tag} dnpipe {$pipeup}\n";
2985
	}
2986
	if (($ipent['dir'] == 'from') || ($ipent['dir'] == 'both')) {
2987
		$rules .= "ether pass in quick l3 from <{$table}> tag {$tag} dnpipe {$pipedown}\n";
2988
	}
2989

    
2990
	captiveportal_load_pfctl("{$cpzoneprefix}_allowedhosts", $host, $rules);
2991

    
2992
	/* return filterdns entry */
2993
	return "pf {$ipent['hostname']} {$table} {$cpzoneprefix}_allowedhosts/{$host}\n";
2994
}
2995

    
2996
function captiveportal_load_pfctl($anchor, $host, $rules) {
2997
	global $g, $cpzone;
2998

    
2999
	if (!empty($rules)) {
3000
		mwexec("/usr/bin/printf \"{$rules}\" | /sbin/pfctl -a {$anchor}/{$host} -f-");
3001
	} else {
3002
		log_error("CP zone ${cpzone}: {$anchor} rules are empty for {$host}");
3003
	}
3004
}
3005

    
3006
function captiveportal_anchor_zerocnt($ip, $anchor = 'auth') {
3007
	global $config, $cpzone;
3008
	$cpzoneprefix = CPPREFIX . $config['captiveportal'][$cpzone]['zoneid'];
3009

    
3010
	pfSense_pf_cp_zerocnt("{$cpzoneprefix}_{$anchor}/{$ip}_32");
3011
}
3012

    
3013
?>
    (1-1/1)