Project

General

Profile

Download (17.7 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php
2
/*
3
	captiveportal.inc
4
	part of m0n0wall (http://m0n0.ch/wall)
5
	
6
	Copyright (C) 2003-2004 Manuel Kasper <mk@neon1.net>.
7
	All rights reserved.
8
	
9
	Redistribution and use in source and binary forms, with or without
10
	modification, are permitted provided that the following conditions are met:
11
	
12
	1. Redistributions of source code must retain the above copyright notice,
13
	   this list of conditions and the following disclaimer.
14
	
15
	2. Redistributions in binary form must reproduce the above copyright
16
	   notice, this list of conditions and the following disclaimer in the
17
	   documentation and/or other materials provided with the distribution.
18
	
19
	THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
20
	INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
21
	AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
22
	AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
23
	OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24
	SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25
	INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26
	CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27
	ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28
	POSSIBILITY OF SUCH DAMAGE.
29
*/
30
	
31
/* include all configuration functions */
32
require_once("functions.inc");
33
require_once("radius_accounting.inc") ;
34

    
35
function captiveportal_configure() {
36
	global $config, $g;
37
	
38
	if (isset($config['captiveportal']['enable']) &&
39
		(($config['captiveportal']['interface'] == "lan") ||
40
			isset($config['interfaces'][$config['captiveportal']['interface']]['enable']))) {
41
	
42
		if ($g['booting'])
43
			echo "Starting captive portal... ";
44
		
45
		/* kill any running mini_httpd */
46
		killbypid("{$g['varrun_path']}/mini_httpd.cp.pid");
47
		killbypid("{$g['varrun_path']}/mini_httpd.cps.pid");
48
		
49
		/* kill any running minicron */
50
		killbypid("{$g['varrun_path']}/minicron.pid");
51
		
52
		/* generate ipfw rules */
53
		$cprules = captiveportal_rules_generate();
54
		
55
		/* make sure ipfw is loaded */
56
		mwexec("/sbin/kldload ipfw");
57
		
58
		/* stop accounting on all clients */
59
		captiveportal_radius_stop_all() ;
60

    
61
		/* remove old information */
62
		unlink_if_exists("{$g['vardb_path']}/captiveportal.nextrule");
63
		unlink_if_exists("{$g['vardb_path']}/captiveportal.db");
64
		unlink_if_exists("{$g['vardb_path']}/captiveportal_mac.db");
65
		unlink_if_exists("{$g['vardb_path']}/captiveportal_ip.db");
66
		unlink_if_exists("{$g['vardb_path']}/captiveportal_radius.db");
67
		
68
		/* write portal page */
69
		if ($config['captiveportal']['page']['htmltext'])
70
			$htmltext = base64_decode($config['captiveportal']['page']['htmltext']);
71
		else {
72
			/* example/template page */
73
			$htmltext = <<<EOD
74
<html>
75
<head>
76
<title>m0n0wall captive portal</title>
77
</head>
78
<body>
79
<h2>m0n0wall captive portal</h2>
80
<p>This is the default captive portal page. Please upload your own custom HTML file on the <em>Services: Captive portal</em> screen in the m0n0wall webGUI.</p>
81
<form method="post" action="\$PORTAL_ACTION\$">
82
  <input name="accept" type="submit" value="Continue">
83
  <input name="redirurl" type="hidden" value="\$PORTAL_REDIRURL\$">
84
</form>
85
</body>
86
</html>
87

    
88
EOD;
89
		}
90

    
91
		$fd = @fopen("{$g['varetc_path']}/captiveportal.html", "w");
92
		if ($fd) {
93
			fwrite($fd, $htmltext);
94
			fclose($fd);	
95
		}
96
		
97
		/* write error page */
98
		if ($config['captiveportal']['page']['errtext'])
99
			$errtext = base64_decode($config['captiveportal']['page']['errtext']);
100
		else {
101
			/* example page */
102
			$errtext = <<<EOD
103
<html>
104
<head>
105
<title>Authentication error</title>
106
</head>
107
<body>
108
<font color="#cc0000"><h2>Authentication error</h2></font>
109
<b>
110
Username and/or password invalid.
111
<br><br>
112
<a href="javascript:history.back()">Go back</a>
113
</b>
114
</body>
115
</html>
116

    
117
EOD;
118
		}
119

    
120
		$fd = @fopen("{$g['varetc_path']}/captiveportal-error.html", "w");
121
		if ($fd) {
122
			fwrite($fd, $errtext);
123
			fclose($fd);	
124
		}
125

    
126
		/* load rules */
127
		mwexec("/sbin/ipfw -f delete set 1");
128
		mwexec("/sbin/ipfw -f delete set 2");
129
		mwexec("/sbin/ipfw -f delete set 3");
130
		
131
		/* XXX - seems like ipfw cannot accept rules directly on stdin,
132
		   so we have to write them to a temporary file first */
133
		$fd = @fopen("{$g['tmp_path']}/ipfw.cp.rules", "w");
134
		if (!$fd) {
135
			printf("Cannot open ipfw.cp.rules in captiveportal_configure()\n");
136
			return 1;
137
		}
138
			
139
		fwrite($fd, $cprules);
140
		fclose($fd);
141
		
142
		mwexec("/sbin/ipfw {$g['tmp_path']}/ipfw.cp.rules");
143
		
144
		unlink("{$g['tmp_path']}/ipfw.cp.rules");
145
		
146
		/* filter on layer2 as well so we can check MAC addresses */
147
		mwexec("/sbin/sysctl net.link.ether.ipfw=1");
148
		
149
		chdir($g['captiveportal_path']);
150
		
151
		/* start web server */
152
		mwexec("/usr/local/sbin/mini_httpd -a -M 0 -u root -maxproc 16" .
153
			" -p 8000 -i {$g['varrun_path']}/mini_httpd.cp.pid");
154
		
155
		/* fire up another one for HTTPS if requested */
156
		if (isset($config['captiveportal']['httpslogin']) &&
157
			$config['captiveportal']['certificate'] && $config['captiveportal']['private-key']) {
158
			
159
			$cert = base64_decode($config['captiveportal']['certificate']);
160
			$key = base64_decode($config['captiveportal']['private-key']);
161
			
162
			$fd = fopen("{$g['varetc_path']}/cert-portal.pem", "w");
163
			if (!$fd) {
164
				printf("Error: cannot open cert-portal.pem in system_webgui_start().\n");
165
				return 1;
166
			}
167
			chmod("{$g['varetc_path']}/cert-portal.pem", 0600);
168
			fwrite($fd, $cert);
169
			fwrite($fd, "\n");
170
			fwrite($fd, $key);
171
			fclose($fd);
172
			
173
			mwexec("/usr/local/sbin/mini_httpd -S -a -M 0 -E {$g['varetc_path']}/cert-portal.pem" .
174
				" -u root -maxproc 16 -p 8001" .
175
				" -i {$g['varrun_path']}/mini_httpd.cps.pid");
176
		}
177
			
178
		/* start pruning process (interval = 60 seconds) */
179
		mwexec("/usr/local/bin/minicron 60 {$g['varrun_path']}/minicron.pid " .
180
			"/etc/rc.prunecaptiveportal");
181
		
182
		/* generate passthru mac database */
183
		captiveportal_passthrumac_configure() ;
184
		/* create allowed ip database and insert ipfw rules to make it so */
185
		captiveportal_allowedip_configure() ;
186

    
187
		/* generate radius server database */
188
		if($config['captiveportal']['radiusip']) {
189
			$radiusip = $config['captiveportal']['radiusip'] ;
190

    
191
			if($config['captiveportal']['radiusport'])
192
				$radiusport = $config['captiveportal']['radiusport'] ;
193
			else
194
				$radiusport = 1812;
195

    
196
			if($config['captiveportal']['radiusacctport'])
197
				$radiusacctport = $config['captiveportal']['radiusacctport'] ;
198
			else
199
				$radiusacctport = 1813;
200

    
201
			$radiuskey = $config['captiveportal']['radiuskey'];
202

    
203
			$fd = @fopen("{$g['vardb_path']}/captiveportal_radius.db", "w");
204
			if (!$fd) {
205
				printf("Error: cannot open radius DB file in captiveportal_configure().\n");
206
				return 1;
207
			} else {
208
				fwrite($fd,$radiusip . "," . $radiusport . "," . $radiusacctport . "," . $radiuskey) ;
209
			}
210
			fclose($fd) ;
211
		}
212

    
213

    
214
		if ($g['booting'])
215
			echo "done\n";
216
		
217
	} else {
218
		killbypid("{$g['varrun_path']}/mini_httpd.cp.pid");
219
		killbypid("{$g['varrun_path']}/minicron.pid");
220
		captiveportal_radius_stop_all() ;
221
		mwexec("/sbin/sysctl net.link.ether.ipfw=0");
222
		if (!isset($config['shaper']['enable'])) {
223
			/* unload ipfw */
224
			mwexec("/sbin/kldunload ipfw");
225
		} else {
226
			/* shaper is on - just remove our rules */
227
			mwexec("/sbin/ipfw -f delete set 1");
228
			mwexec("/sbin/ipfw -f delete set 2");
229
			mwexec("/sbin/ipfw -f delete set 3");
230
		}
231
	}
232
	
233
	return 0;
234
}
235

    
236
function captiveportal_rules_generate() {
237
	global $config, $g;
238
	
239
	$cpifn = $config['captiveportal']['interface'];
240
	$cpif = $config['interfaces'][$cpifn]['if'];
241
	$cpip = $config['interfaces'][$cpifn]['ipaddr'];
242

    
243
	/* note: the captive portal daemon inserts all pass rules for authenticated
244
	   clients as skipto 50000 rules to make traffic shaping work */
245

    
246
	$cprules = "";
247
	
248
	/* captive portal on LAN interface? */
249
	if ($cpifn == "lan") {
250
		/* add anti-lockout rules */
251
		$cprules .= <<<EOD
252
add 500 set 1 pass all from $cpip to any out via $cpif
253
add 501 set 1 pass all from any to $cpip in via $cpif
254

    
255
EOD;
256
	}
257

    
258
	$cprules .= <<<EOD
259
# skip to traffic shaper if not on captive portal interface
260
add 1000 set 1 skipto 50000 all from any to any not layer2 not via $cpif
261
# pass all layer2 traffic on other interfaces
262
add 1001 set 1 pass layer2 not via $cpif
263

    
264
# layer 2: pass ARP
265
add 1100 set 1 pass layer2 mac-type arp
266
# layer 2: block anything else non-IP
267
add 1101 set 1 deny layer2 not mac-type ip
268
# layer 2: check if MAC addresses of authenticated clients are correct
269
add 1102 set 1 skipto 20000 layer2
270

    
271
# allow access to our DHCP server (which needs to be able to ping clients as well)
272
add 1200 set 1 pass udp from any 68 to 255.255.255.255 67 in
273
add 1201 set 1 pass udp from any 68 to $cpip 67 in
274
add 1202 set 1 pass udp from $cpip 67 to any 68 out
275
add 1203 set 1 pass icmp from $cpip to any out icmptype 8
276
add 1204 set 1 pass icmp from any to $cpip in icmptype 0
277

    
278
# allow access to our DNS forwarder
279
add 1300 set 1 pass udp from any to $cpip 53 in
280
add 1301 set 1 pass udp from $cpip 53 to any out
281

    
282
# allow access to our web server
283
add 1302 set 1 pass tcp from any to $cpip 8000 in
284
add 1303 set 1 pass tcp from $cpip 8000 to any out
285

    
286
EOD;
287

    
288
	if (isset($config['captiveportal']['httpslogin'])) {
289
		$cprules .= <<<EOD
290
add 1304 set 1 pass tcp from any to $cpip 8001 in
291
add 1305 set 1 pass tcp from $cpip 8001 to any out
292

    
293
EOD;
294
	}
295
	
296
	$cprules .= <<<EOD
297

    
298
# ... 10000-19899: rules per authenticated client go here...
299

    
300
# redirect non-authenticated clients to captive portal
301
add 19900 set 1 fwd 127.0.0.1,8000 tcp from any to any 80 in
302
# let the responses from the captive portal web server back out
303
add 19901 set 1 pass tcp from any 80 to any out
304
# block everything else
305
add 19902 set 1 deny all from any to any
306

    
307
# ... 20000-29899: layer2 block rules per authenticated client go here...
308

    
309
# pass everything else on layer2
310
add 29900 set 1 pass all from any to any layer2
311

    
312
EOD;
313

    
314
	return $cprules;
315
}
316

    
317
/* remove clients that have been around for longer than the specified amount of time */
318
/* db file structure: timestamp,ipfw_rule_no,clientip,clientmac,username,sessionid */
319
function captiveportal_prune_old() {
320
	
321
	global $g, $config;
322
	
323
	/* check for expired entries */
324
	if ($config['captiveportal']['timeout'])
325
		$timeout = $config['captiveportal']['timeout'] * 60;
326
	else
327
		$timeout = 0;
328
		
329
	if ($config['captiveportal']['idletimeout'])
330
		$idletimeout = $config['captiveportal']['idletimeout'] * 60;
331
	else
332
		$idletimeout = 0;
333
	
334
	if (!$timeout && !$idletimeout)
335
		return;
336
	
337
	captiveportal_lock();
338
	
339
	/* read database */
340
	$cpdb = captiveportal_read_db();
341
	
342
	$radiusservers = captiveportal_get_radius_servers();
343
	
344
	for ($i = 0; $i < count($cpdb); $i++) {
345
		
346
		$timedout = false;
347
		
348
		/* hard timeout? */
349
		if ($timeout) {
350
			if ((time() - $cpdb[$i][0]) >= $timeout)
351
				$timedout = true;	
352
		}
353
		
354
		/* if an idle timeout is specified, get last activity timestamp from ipfw */
355
		if (!$timedout && $idletimeout) {
356
			$lastact = captiveportal_get_last_activity($cpdb[$i][1]);
357
			if ($lastact && ((time() - $lastact) >= $idletimeout))
358
				$timedout = true;
359
		}
360
		
361
		if ($timedout) {
362
			/* this client needs to be deleted - remove ipfw rules */
363
			if (isset($config['captiveportal']['radacct_enable']) && isset($radiusservers[0])) {
364
				RADIUS_ACCOUNTING_STOP($cpdb[$i][1], // ruleno
365
									   $cpdb[$i][4], // username
366
									   $cpdb[$i][5], // sessionid
367
									   $cpdb[$i][0], // start time
368
									   $radiusservers[0]['ipaddr'],
369
									   $radiusservers[0]['acctport'],
370
									   $radiusservers[0]['key']);
371
			}
372
			mwexec("/sbin/ipfw delete " . $cpdb[$i][1] . " " . ($cpdb[$i][1]+10000));
373
			unset($cpdb[$i]);
374
		}
375
	}
376
	
377
	/* write database */
378
	captiveportal_write_db($cpdb);
379
	
380
	captiveportal_unlock();
381
}
382

    
383
/* remove a single client by ipfw rule number */
384
function captiveportal_disconnect_client($id) {
385
	
386
	global $g, $config;
387
	
388
	captiveportal_lock();
389
	
390
	/* read database */
391
	$cpdb = captiveportal_read_db();
392
	$radiusservers = captiveportal_get_radius_servers();
393
	
394
	/* find entry */	
395
	for ($i = 0; $i < count($cpdb); $i++) {
396
		if ($cpdb[$i][1] == $id) {
397
			/* this client needs to be deleted - remove ipfw rules */
398
			if (isset($config['captiveportal']['radacct_enable']) && isset($radiusservers[0])) {
399
				RADIUS_ACCOUNTING_STOP($cpdb[$i][1], // ruleno
400
									   $cpdb[$i][4], // username
401
									   $cpdb[$i][5], // sessionid
402
									   $cpdb[$i][0], // start time
403
									   $radiusservers[0]['ipaddr'],
404
									   $radiusservers[0]['acctport'],
405
									   $radiusservers[0]['key']);
406
			}
407
			mwexec("/sbin/ipfw delete " . $cpdb[$i][1] . " " . ($cpdb[$i][1]+10000));
408
			unset($cpdb[$i]);
409
			break;
410
		}
411
	}
412
	
413
	/* write database */
414
	captiveportal_write_db($cpdb);
415
	
416
	captiveportal_unlock();
417
}
418

    
419
/* send RADIUS acct stop for all current clients */
420
function captiveportal_radius_stop_all() {
421
	global $g, $config;
422

    
423
	captiveportal_lock() ;
424
	$cpdb = captiveportal_read_db() ;
425
	
426
	$radiusservers = captiveportal_get_radius_servers();
427
	
428
	if (isset($radiusservers[0])) {
429
		for ($i = 0; $i < count($cpdb); $i++) {
430
			RADIUS_ACCOUNTING_STOP($cpdb[$i][1], // ruleno
431
								   $cpdb[$i][4], // username
432
								   $cpdb[$i][5], // sessionid
433
								   $cpdb[$i][0], // start time
434
								   $radiusservers[0]['ipaddr'],
435
								   $radiusservers[0]['acctport'],
436
								   $radiusservers[0]['key']);
437
		}
438
	}
439
	captiveportal_unlock() ;
440
}
441

    
442
function captiveportal_passthrumac_configure() {
443
	global $config, $g;
444
	
445
	/* clear out passthru macs, if necessary */
446
	if (file_exists("{$g['vardb_path']}/captiveportal_mac.db")) {
447
		unlink("{$g['vardb_path']}/captiveportal_mac.db");
448
	}
449
	
450
	if (is_array($config['captiveportal']['passthrumac'])) {
451
		
452
		$fd = @fopen("{$g['vardb_path']}/captiveportal_mac.db", "w");
453
		if (!$fd) {
454
			printf("Error: cannot open passthru mac DB file in captiveportal_passthrumac_configure().\n");
455
			return 1;		
456
		}
457
		
458
		foreach ($config['captiveportal']['passthrumac'] as $macent) {
459
			/* record passthru mac so it can be recognized and let thru */
460
			fwrite($fd, $macent['mac'] . "\n");
461
		}
462
		
463
		fclose($fd); 
464
	}
465
	
466
	return 0;
467
}
468

    
469
function captiveportal_allowedip_configure() {
470
	global $config, $g;
471
	
472
	captiveportal_lock() ;
473

    
474
	/* clear out existing allowed ips, if necessary */
475
	if (file_exists("{$g['vardb_path']}/captiveportal_ip.db")) {
476
		$fd = @fopen("{$g['vardb_path']}/captiveportal_ip.db", "r");
477
		if ($fd) {
478
			while (!feof($fd)) {
479
				$line = trim(fgets($fd));
480
				if($line) {
481
					list($ip,$rule) = explode(",",$line);
482
					mwexec("/sbin/ipfw delete $rule") ;
483
				}	
484
			}
485
		}
486
		fclose($fd) ;
487
		unlink("{$g['vardb_path']}/captiveportal_ip.db");
488
	}
489

    
490
	/* get next ipfw rule number */
491
	if (file_exists("{$g['vardb_path']}/captiveportal.nextrule"))
492
		$ruleno = trim(file_get_contents("{$g['vardb_path']}/captiveportal.nextrule"));
493
	if (!$ruleno)
494
		$ruleno = 10000;	/* first rule number */
495
	
496
	if (is_array($config['captiveportal']['allowedip'])) {
497
		
498
		$fd = @fopen("{$g['vardb_path']}/captiveportal_ip.db", "w");
499
		if (!$fd) {
500
			printf("Error: cannot open allowed ip DB file in captiveportal_allowedip_configure().\n");
501
			captiveportal_unlock() ;
502
			return 1;		
503
		}
504
		
505
		foreach ($config['captiveportal']['allowedip'] as $ipent) {
506
			/* record allowed ip so it can be recognized and removed later */
507
			fwrite($fd, $ipent['ip'] . "," . $ruleno ."\n");
508
			/* insert ipfw rule to allow ip thru */
509
			if($ipent['dir'] == "from") {
510
				mwexec("/sbin/ipfw add $ruleno set 2 skipto 50000 ip from ".$ipent['ip']." to any in") ;
511
				mwexec("/sbin/ipfw add $ruleno set 2 skipto 50000 ip from any to ".$ipent['ip']." out") ;
512
			} else {
513
				mwexec("/sbin/ipfw add $ruleno set 2 skipto 50000 ip from any to ".$ipent['ip']." in") ;
514
				mwexec("/sbin/ipfw add $ruleno set 2 skipto 50000 ip from ".$ipent['ip']." to any out") ;
515
			}
516
			$ruleno++ ;
517
			if ($ruleno > 19899)
518
				$ruleno = 10000;
519
		}
520
		
521
		fclose($fd); 
522

    
523
		/* write next rule number */
524
		$fd = @fopen("{$g['vardb_path']}/captiveportal.nextrule", "w");
525
		if ($fd) {
526
			fwrite($fd, $ruleno);
527
			fclose($fd);
528
		}
529
	}
530
	
531
	captiveportal_unlock() ;
532
	return 0;
533
}
534

    
535
/* get last activity timestamp given ipfw rule number */
536
function captiveportal_get_last_activity($ruleno) {
537
	
538
	exec("/sbin/ipfw -T list {$ruleno} 2>/dev/null", $ipfwoutput);
539
	
540
	/* in */
541
	if ($ipfwoutput[0]) {
542
		$ri = explode(" ", $ipfwoutput[0]);
543
		if ($ri[1])
544
			return $ri[1];
545
	}
546
	
547
	return 0;
548
}
549

    
550
/* read captive portal DB into array */
551
function captiveportal_read_db() {
552
	
553
	global $g;
554
	
555
	$cpdb = array();
556
	$fd = @fopen("{$g['vardb_path']}/captiveportal.db", "r");
557
	if ($fd) {
558
		while (!feof($fd)) {
559
			$line = trim(fgets($fd));
560
			if ($line) {
561
				$cpdb[] = explode(",", $line);
562
			}	
563
		}
564
		fclose($fd);
565
	}
566
	return $cpdb;
567
}
568

    
569
/* write captive portal DB */
570
function captiveportal_write_db($cpdb) {
571
	
572
	global $g;
573
	
574
	$fd = @fopen("{$g['vardb_path']}/captiveportal.db", "w");
575
	if ($fd) {
576
		foreach ($cpdb as $cpent) {
577
			fwrite($fd, join(",", $cpent) . "\n");
578
		}
579
		fclose($fd);
580
	}
581
}
582

    
583
/* read RADIUS servers into array */
584
function captiveportal_get_radius_servers() {
585
	
586
	global $g;
587
	
588
	if (file_exists("{$g['vardb_path']}/captiveportal_radius.db")) {
589
	   	$fd = @fopen("{$g['vardb_path']}/captiveportal_radius.db","r");
590
		if ($fd) {
591
			$radiusservers = array();
592
			while (!feof($fd)) {
593
				$line = trim(fgets($fd));
594
				if ($line) {
595
					$radsrv = array();
596
					list($radsrv['ipaddr'],$radsrv['port'],$radsrv['acctport'],$radsrv['key']) = explode(",",$line);
597
					$radiusservers[] = $radsrv;
598
				}
599
			}
600
			fclose($fd);
601
			
602
			return $radiusservers;
603
		}
604
	}
605
	
606
	return false;
607
}
608

    
609
/* lock captive portal information, decide that the lock file is stale after
610
   10 seconds */
611
function captiveportal_lock() {
612
	
613
	global $g;
614
	
615
	$lockfile = "{$g['varrun_path']}/captiveportal.lock";
616
	
617
	$n = 0;
618
	while ($n < 10) {
619
		/* open the lock file in append mode to avoid race condition */
620
		if ($fd = @fopen($lockfile, "x")) {
621
			/* succeeded */
622
			fclose($fd);
623
			return;
624
		} else {
625
			/* file locked, wait and try again */
626
			sleep(1);
627
			$n++;
628
		}
629
	}
630
}
631

    
632
/* unlock configuration file */
633
function captiveportal_unlock() {
634
	
635
	global $g;
636
	
637
	$lockfile = "{$g['varrun_path']}/captiveportal.lock";
638
	
639
	if (file_exists($lockfile))
640
		unlink($lockfile);
641
}
642

    
643
?>
(1-1/12)