Project

General

Profile

Download (24.8 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php
2
/*
3
	Copyright (C) 2010-2012 Ermal Luci <eri@pfsense.org>
4
	Copyright (C) 2010 Scott Ullrich <sullrich@gmail.com>
5
    Copyright (C) 2007 Marcel Wiget <mwiget@mac.com>
6
    All rights reserved.
7
    
8
    Redistribution and use in source and binary forms, with or without
9
    modification, are permitted provided that the following conditions are met:
10
    
11
    1. Redistributions of source code must retain the above copyright notice,
12
       this list of conditions and the following disclaimer.
13
    
14
    2. Redistributions in binary form must reproduce the above copyright
15
       notice, this list of conditions and the following disclaimer in the
16
       documentation and/or other materials provided with the distribution.
17
    
18
    THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
19
    INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
20
    AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
21
    AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
22
    OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23
    SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24
    INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25
    CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26
    ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
    POSSIBILITY OF SUCH DAMAGE.
28

    
29
*/
30

    
31
/*
32
	pfSense_BUILDER_BINARIES:	/usr/local/bin/voucher
33
	pfSense_MODULE:	captiveportal
34
*/
35

    
36
/* include all configuration functions */
37
if(!function_exists('captiveportal_syslog'))
38
	require_once("captiveportal.inc");
39

    
40
function xmlrpc_sync_voucher_expire($vouchers, $syncip, $port, $password, $username) {
41
	global $g, $config, $cpzone;
42
	require_once("xmlrpc.inc");
43
	if($port == "443") 
44
		$url = "https://{$syncip}";
45
	else 
46
		$url = "http://{$syncip}";
47

    
48
	/* Construct code that is run on remote machine */
49
	$method = 'pfsense.exec_php';
50
	$execcmd  = <<<EOF
51
	require_once('/etc/inc/captiveportal.inc');
52
	require_once('/etc/inc/voucher.inc');
53
	\$cpzone = $cpzone;
54
	voucher_expire(\$vouchers);
55

    
56
EOF;
57

    
58
	/* assemble xmlrpc payload */
59
	$params = array(
60
		XML_RPC_encode($password),
61
		XML_RPC_encode($execcmd)
62
	);
63

    
64
	log_error("Captive Portal Voucher XMLRPC sync data {$url}:{$port}.");
65
	$msg = new XML_RPC_Message($method, $params);
66
	$cli = new XML_RPC_Client('/xmlrpc.php', $url, $port);
67
	$cli->setCredentials($username, $password);
68
	$resp = $cli->send($msg, "250");
69
	if(!is_object($resp)) {
70
		$error = "A communications error occurred while attempting CaptivePortalVoucherSync XMLRPC sync with {$url}:{$port} (pfsense.exec_php).";
71
		log_error($error);
72
		file_notice("CaptivePortalVoucherSync", $error, "Communications error occurred", "");
73
		return false;
74
	} elseif($resp->faultCode()) {
75
		$error = "An error code was received while attempting CaptivePortalVoucherSync XMLRPC sync with {$url}:{$port} - Code " . $resp->faultCode() . ": " . $resp->faultString();
76
		log_error($error);
77
		file_notice("CaptivePortalVoucherSync", $error, "Error code received", "");
78
		return false;
79
	} else {
80
		log_error("CaptivePortalVoucherSync XMLRPC reload data success with {$url}:{$port} (pfsense.exec_php).");
81
	}
82

    
83
	$toreturn =  XML_RPC_Decode($resp->value());
84

    
85
	return $toreturn;
86
}
87

    
88
function xmlrpc_sync_voucher_disconnect($dbent, $syncip, $port, $password, $username, $term_cause = "1", $stop_time = null) {
89
	global $g, $config, $cpzone;
90
	require_once("xmlrpc.inc");
91
	if($port == "443") 
92
		$url = "https://{$syncip}";
93
	else 
94
		$url = "http://{$syncip}";
95

    
96
	/* Construct code that is run on remote machine */
97
	$method = 'pfsense.exec_php';
98
	$execcmd  = <<<EOF
99
	require_once('/etc/inc/captiveportal.inc');
100
	require_once('/etc/inc/voucher.inc');
101
	\$cpzone = $cpzone;
102
	\$radiusservers = captiveportal_get_radius_servers();
103
	captiveportal_disconnect(\$dbent, \$radiusservers, \$term_cause, \$stop_time);
104

    
105
EOF;
106

    
107
	/* assemble xmlrpc payload */
108
	$params = array(
109
		XML_RPC_encode($password),
110
		XML_RPC_encode($execcmd)
111
	);
112

    
113
	log_error("Captive Portal Voucher XMLRPC sync data {$url}:{$port}.");
114
	$msg = new XML_RPC_Message($method, $params);
115
	$cli = new XML_RPC_Client('/xmlrpc.php', $url, $port);
116
	$cli->setCredentials($username, $password);
117
	$resp = $cli->send($msg, "250");
118
	if(!is_object($resp)) {
119
		$error = "A communications error occurred while attempting CaptivePortalVoucherSync XMLRPC sync with {$url}:{$port} (pfsense.exec_php).";
120
		log_error($error);
121
		file_notice("CaptivePortalVoucherSync", $error, "Communications error occurred", "");
122
		return false;
123
	} elseif($resp->faultCode()) {
124
		$error = "An error code was received while attempting CaptivePortalVoucherSync XMLRPC sync with {$url}:{$port} - Code " . $resp->faultCode() . ": " . $resp->faultString();
125
		log_error($error);
126
		file_notice("CaptivePortalVoucherSync", $error, "Error code received", "");
127
		return false;
128
	} else {
129
		log_error("CaptivePortalVoucherSync XMLRPC reload data success with {$url}:{$port} (pfsense.exec_php).");
130
	}
131

    
132
	$toreturn =  XML_RPC_Decode($resp->value());
133

    
134
	return $toreturn;
135
}
136

    
137
function xmlrpc_sync_used_voucher($voucher_received, $syncip, $port, $password, $username) {
138
	global $g, $config, $cpzone;
139
	require_once("xmlrpc.inc");
140
	if($port == "443") 
141
		$url = "https://{$syncip}";
142
	else 
143
		$url = "http://{$syncip}";
144

    
145
	/* Construct code that is run on remote machine */
146
	$method = 'pfsense.exec_php';
147
	$execcmd  = <<<EOF
148
	require_once('/etc/inc/voucher.inc');
149
	\$cpzone = $cpzone;
150
	\$timeleft = voucher_auth({$voucher_received});
151
	\$toreturn = array();
152
	\$toreturn['timeleft'] = \$timeleft;
153
	\$toreturn['voucher']['roll'] = \$config['voucher'][$cpzone]['roll'];
154

    
155
EOF;
156

    
157
	/* assemble xmlrpc payload */
158
	$params = array(
159
		XML_RPC_encode($password),
160
		XML_RPC_encode($execcmd)
161
	);
162

    
163
	log_error("Captive Portal Voucher XMLRPC sync data {$url}:{$port}.");
164
	$msg = new XML_RPC_Message($method, $params);
165
	$cli = new XML_RPC_Client('/xmlrpc.php', $url, $port);
166
	$cli->setCredentials($username, $password);
167
	$resp = $cli->send($msg, "250");
168
	if(!is_object($resp)) {
169
		$error = "A communications error occurred while attempting CaptivePortalVoucherSync XMLRPC sync with {$url}:{$port} (pfsense.exec_php).";
170
		log_error($error);
171
		file_notice("CaptivePortalVoucherSync", $error, "Communications error occurred", "");
172
		return 0; // $timeleft
173
	} elseif($resp->faultCode()) {
174
		$error = "An error code was received while attempting CaptivePortalVoucherSync XMLRPC sync with {$url}:{$port} - Code " . $resp->faultCode() . ": " . $resp->faultString();
175
		log_error($error);
176
		file_notice("CaptivePortalVoucherSync", $error, "Error code received", "");
177
		return 0; // $timeleft
178
	} else {
179
		log_error("CaptivePortalVoucherSync XMLRPC reload data success with {$url}:{$port} (pfsense.exec_php).");
180
	}
181
	$toreturn =  XML_RPC_Decode($resp->value());
182
	if (is_array($toreturn['voucher']) && (count($toreturn['voucher'][$cpzone]['roll']) <> count($config['voucher'][$cpzone]['roll']))) {
183
		$config['voucher'][$cpzone]['roll'] = $toreturn['voucher']['roll'];
184
		write_config("Captive Portal Voucher database synchronized with {$url}");
185
		voucher_configure_zone(true);
186
	}
187

    
188
	return $toreturn['timeleft'];
189
}
190

    
191
function voucher_expire($voucher_received) {
192
	global $g, $config, $cpzone;
193

    
194
	$voucherlck = lock("voucher{$cpzone}", LOCK_EX);
195

    
196
	// XMLRPC Call over to the master Voucher node
197
	if(!empty($config['voucher'][$cpzone]['vouchersyncdbip'])) {
198
		$syncip   = $config['voucher'][$cpzone]['vouchersyncdbip'];
199
		$syncport = $config['voucher'][$cpzone]['vouchersyncport'];
200
		$syncpass = $config['voucher'][$cpzone]['vouchersyncpass'];
201
		$vouchersyncusername = $config['voucher'][$cpzone]['vouchersyncusername'];
202
		xmlrpc_sync_voucher_expire($voucher_received, $syncip, $syncport, $syncpass, $vouchersyncusername);
203
	}
204

    
205
	// read rolls into assoc array with rollid as key and minutes as value
206
	$tickets_per_roll = array();
207
	$minutes_per_roll = array();
208
	if (is_array($config['voucher'][$cpzone]['roll'])) {
209
		foreach ($config['voucher'][$cpzone]['roll'] as $rollent) {
210
			$tickets_per_roll[$rollent['number']] = $rollent['count'];
211
			$minutes_per_roll[$rollent['number']] = $rollent['minutes'];
212
		}
213
	}
214

    
215
	// split into an array. Useful for multiple vouchers given
216
	$a_vouchers_received = preg_split("/[\t\n\r ]+/s", $voucher_received); 
217
	$active_dirty = false;
218
	$unsetindexes[] = array();
219

    
220
	// go through all received vouchers, check their valid and extract
221
	// Roll# and Ticket# using the external readvoucher binary
222
	foreach ($a_vouchers_received as $voucher) {
223
		$v = escapeshellarg($voucher);
224
		if (strlen($voucher) < 3)
225
			continue;   // seems too short to be a voucher!
226

    
227
		$result = exec("/usr/local/bin/voucher -c {$g['varetc_path']}/voucher_{$cpzone}.cfg -k {$g['varetc_path']}/voucher_{$cpzone}.public -- $v");
228
		list($status, $roll, $nr) = explode(" ", $result);
229
		if ($status == "OK") {
230
			// check if we have this ticket on a registered roll for this ticket 
231
			if ($tickets_per_roll[$roll] && ($nr <= $tickets_per_roll[$roll])) {
232
				// voucher is from a registered roll. 
233
				if (!isset($active_vouchers[$roll]))
234
					$active_vouchers[$roll] = voucher_read_active_db($roll);
235
				// valid voucher. Store roll# and ticket#
236
				if (!empty($active_vouchers[$roll][$voucher])) {
237
					$active_dirty = true;
238
					unset($active_vouchers[$roll][$voucher]);
239
				}
240
				// check if voucher already marked as used
241
				if (!isset($bitstring[$roll]))
242
					$bitstring[$roll] = voucher_read_used_db($roll);
243
				$pos = $nr >> 3; // divide by 8 -> octet
244
				$mask = 1 << ($nr % 8);
245
				// mark bit for this voucher as used
246
				if (!(ord($bitstring[$roll][$pos]) & $mask))
247
					$bitstring[$roll][$pos] = chr(ord($bitstring[$roll][$pos]) | $mask);
248
				captiveportal_syslog("{$voucher} ({$roll}/{$nr}) forced to expire");
249

    
250
				/* Check if this voucher has any active sessions */
251
				$cpentry = captiveportal_read_db("WHERE username = '{$voucher}'");
252
				if (!empty($cpentry)) {
253
					captiveportal_disconnect($cpentry,null,13);
254
					captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"FORCLY TERMINATING VOUCHER {$voucher} SESSION");
255
					$unsetindexes[] = $cpentry[5];
256
				}
257
			} else
258
				captiveportal_syslog("$voucher ($roll/$nr): not found on any registererd Roll");
259
		} else
260
			// hmm, thats weird ... not what I expected
261
			captiveportal_syslog("$voucher invalid: $result !!");
262
	}
263

    
264
	// Refresh active DBs
265
	if ($active_dirty == true) {
266
		foreach ($active_vouchers as $roll => $active)
267
			voucher_write_active_db($roll, $active);
268

    
269
		/* Triger a sync of the vouchers on config */
270
		send_event("service sync vouchers");
271
	}
272

    
273
	// Write back the used DB's
274
	if (is_array($bitstring)) {
275
		foreach ($bitstring as $roll => $used) {
276
			if(is_array($used)) {
277
				foreach($used as $u)
278
					voucher_write_used_db($roll, base64_encode($u));
279
			} else {
280
				voucher_write_used_db($roll, base64_encode($used));
281
			}
282
		}
283
	}
284

    
285
	unlock($voucherlck);
286

    
287
	/* Write database */
288
	if (!empty($unsetindexes))
289
		captiveportal_remove_entries($unsetindexes);
290

    
291
	return true;
292
}
293

    
294
/* 
295
 * Authenticate a voucher and return the remaining time credit in minutes
296
 * if $test is set, don't mark the voucher as used nor add it to the list
297
 * of active vouchers
298
 * If $test is set, simply test the voucher. Don't change anything
299
 * but return a more verbose error and result message back
300
 */
301
function voucher_auth($voucher_received, $test = 0) {
302
	global $g, $config, $cpzone, $dbc;
303

    
304
	if (!isset($config['voucher'][$cpzone]['enable']))
305
		return 0;
306

    
307
	$voucherlck = lock("voucher{$cpzone}", LOCK_EX);
308

    
309
	// XMLRPC Call over to the master Voucher node
310
	if(!empty($config['voucher'][$cpzone]['vouchersyncdbip'])) {
311
		$syncip   = $config['voucher'][$cpzone]['vouchersyncdbip'];
312
		$syncport = $config['voucher'][$cpzone]['vouchersyncport'];
313
		$syncpass = $config['voucher'][$cpzone]['vouchersyncpass'];
314
		$vouchersyncusername = $config['voucher'][$cpzone]['vouchersyncusername'];
315
		$remote_time_used = xmlrpc_sync_used_voucher($voucher_received, $syncip, $syncport, $syncpass, $vouchersyncusername);
316
	}
317

    
318
	// read rolls into assoc array with rollid as key and minutes as value
319
	$tickets_per_roll = array();
320
	$minutes_per_roll = array();
321
	if (is_array($config['voucher'][$cpzone]['roll'])) {
322
		foreach ($config['voucher'][$cpzone]['roll'] as $rollent) {
323
			$tickets_per_roll[$rollent['number']] = $rollent['count'];
324
			$minutes_per_roll[$rollent['number']] = $rollent['minutes'];
325
		}
326
	}
327

    
328
	// split into an array. Useful for multiple vouchers given
329
	$a_vouchers_received = preg_split("/[\t\n\r ]+/s", $voucher_received); 
330
	$error = 0;
331
	$test_result = array();     // used to display for voucher test option in GUI
332
	$total_minutes = 0;
333
	$first_voucher = "";
334
	$first_voucher_roll = 0;
335

    
336
	// go through all received vouchers, check their valid and extract
337
	// Roll# and Ticket# using the external readvoucher binary
338
	foreach ($a_vouchers_received as $voucher) {
339
		$v = escapeshellarg($voucher);
340
		if (strlen($voucher) < 3)
341
			continue;   // seems too short to be a voucher!
342

    
343
		$result = exec("/usr/local/bin/voucher -c {$g['varetc_path']}/voucher_{$cpzone}.cfg -k {$g['varetc_path']}/voucher_{$cpzone}.public -- $v");
344
		list($status, $roll, $nr) = explode(" ", $result);
345
		if ($status == "OK") {
346
			if (!$first_voucher) {
347
				// store first voucher. Thats the one we give the timecredit
348
				$first_voucher = $voucher;
349
				$first_voucher_roll = $roll;
350
			}
351
			// check if we have this ticket on a registered roll for this ticket 
352
			if ($tickets_per_roll[$roll] && ($nr <= $tickets_per_roll[$roll])) {
353
				// voucher is from a registered roll. 
354
				if (!isset($active_vouchers[$roll]))
355
					$active_vouchers[$roll] = voucher_read_active_db($roll);
356
				// valid voucher. Store roll# and ticket#
357
				if (!empty($active_vouchers[$roll][$voucher])) {
358
					list($timestamp,$minutes) = explode(",", $active_vouchers[$roll][$voucher]);
359
					// we have an already active voucher here.
360
					$remaining = intval((($timestamp + (60*$minutes)) - time())/60);
361
					$test_result[] = sprintf(gettext('%1$s (%2$s/%3$s) active and good for %4$d Minutes'), $voucher, $roll, $nr, $remaining);
362
					$total_minutes += $remaining;
363
				} else {
364
					// voucher not used. Check if ticket Id is on the roll (not too high)
365
					// and if the ticket is marked used.
366
					// check if voucher already marked as used
367
					if (!isset($bitstring[$roll]))
368
						$bitstring[$roll] = voucher_read_used_db($roll);
369
					$pos = $nr >> 3; // divide by 8 -> octet
370
					$mask = 1 << ($nr % 8);
371
					if (ord($bitstring[$roll][$pos]) & $mask) {
372
						$test_result[] = "$voucher ($roll/$nr) already used and expired";
373
						captiveportal_syslog("$voucher ($roll/$nr) already used and expired");
374
						$total_minutes = -1;    // voucher expired
375
						$error++;
376
					} else {
377
						// mark bit for this voucher as used
378
						$bitstring[$roll][$pos] = chr(ord($bitstring[$roll][$pos]) | $mask);
379
						$test_result[] = "$voucher ($roll/$nr) good for {$minutes_per_roll[$roll]} Minutes";
380
						$total_minutes += $minutes_per_roll[$roll];
381
					}
382
				}
383
			} else {
384
				$test_result[] = "$voucher ($roll/$nr): not found on any registererd Roll";
385
				captiveportal_syslog("$voucher ($roll/$nr): not found on any registererd Roll");
386
			}
387
		} else {
388
			// hmm, thats weird ... not what I expected
389
			$test_result[] = "$voucher invalid: $result !!";
390
			captiveportal_syslog("$voucher invalid: $result !!");
391
			$error++;
392
		}
393
	}
394

    
395
	// if this was a test call, we're done. Return the result.
396
	if ($test) {
397
		if ($error) {
398
			$test_result[] = gettext("Access denied!");
399
		} else {
400
			$test_result[] = sprintf(gettext("Access granted for %d Minutes in total."),$total_minutes);
401
		}
402
		unlock($voucherlck);
403

    
404
		return $test_result;
405
	}
406

    
407
	// if we had an error (one of the vouchers is invalid), return 0.
408
	// Discussion: we could return the time remaining for good vouchers, but then
409
	// the user wouldn't know that he used at least one invalid voucher.
410
	if ($error) {
411
		unlock($voucherlck);
412
		if ($total_minutes > 0)     // probably not needed, but want to make sure
413
			$total_minutes = 0;     // we only report -1 (expired) or 0 (no access)
414
		return $total_minutes;       // well, at least one voucher had errors. Say NO ACCESS
415
	}
416

    
417
	// If we did a XMLRPC sync earlier check the timeleft
418
	if (!empty($config['voucher'][$cpzone]['vouchersyncdbip'])) 
419
		if($remote_time_used < $total_minutes) 
420
			$total_minutes = $remote_time_used;
421

    
422
	// All given vouchers were valid and this isn't simply a test.
423
	// Write back the used DB's
424
	if (is_array($bitstring)) {
425
		foreach ($bitstring as $roll => $used) {
426
			if(is_array($used)) {
427
				foreach($used as $u)
428
					voucher_write_used_db($roll, base64_encode($u));
429
			} else {
430
				voucher_write_used_db($roll, base64_encode($used));
431
			}
432
		}
433
	}
434

    
435
	// Active DB: we only add the first voucher if multiple given
436
	// and give that one all the time credit. This allows the user to logout and
437
	// log in later using just the first voucher. It also keeps username limited
438
	// to one voucher and that voucher shows the correct time credit in 'active vouchers'
439
	if (!empty($active_vouchers[$first_voucher_roll][$first_voucher])) {
440
		list($timestamp, $minutes) = explode(",", $active_vouchers[$first_voucher_roll][$first_voucher]);
441
	} else {
442
		$timestamp = time();    // new voucher
443
		$minutes = $total_minutes;
444
	}
445

    
446
	$active_vouchers[$first_voucher_roll][$first_voucher] = "$timestamp,$minutes";
447
	voucher_write_active_db($first_voucher_roll, $active_vouchers[$first_voucher_roll]);
448

    
449
	/* Triger a sync of the vouchers on config */
450
	send_event("service sync vouchers");
451

    
452
	unlock($voucherlck);
453

    
454
	return $total_minutes;
455
}
456

    
457
function voucher_configure($sync = false) {
458
	global $config, $g, $cpzone;
459

    
460
	if (is_array($config['voucher'])) {
461
		foreach ($config['voucher'] as $voucherzone => $vcfg) {
462
			if ($g['booting'])
463
			    echo gettext("Enabling voucher support... ");
464
			$cpzone = $voucherzone;
465
			$error = voucher_configure_zone($sync);
466
			if ($g['booting']) {
467
				if ($error)
468
					echo "error\n";
469
				else
470
					echo "done\n";
471
			}
472
		}
473
	}
474
}
475

    
476
function voucher_configure_zone($sync = false) {
477
	global $config, $g, $cpzone;
478

    
479
	if (!isset($config['voucher'][$cpzone]['enable']))
480
		return 0;
481

    
482
	if ($sync == true)
483
	    captiveportal_syslog("Writing voucher db from sync data...");
484

    
485
	$voucherlck = lock("voucher{$cpzone}", LOCK_EX);
486

    
487
        /* write public key used to verify vouchers */
488
        $pubkey = base64_decode($config['voucher'][$cpzone]['publickey']);
489
        $fd = fopen("{$g['varetc_path']}/voucher_{$cpzone}.public", "w");
490
        if (!$fd) {
491
            captiveportal_syslog("Voucher error: cannot write voucher.public\n");
492
	    unlock($voucherlck);
493
            return 1;
494
        }
495
        fwrite($fd, $pubkey);
496
        fclose($fd);
497
        @chmod("{$g['varetc_path']}/voucher_{$cpzone}.public", 0600);
498

    
499
        /* write config file used by voucher binary to decode vouchers */
500
        $fd = fopen("{$g['varetc_path']}/voucher_{$cpzone}.cfg", "w");
501
        if (!$fd) {
502
	    printf(gettext("Error: cannot write voucher.cfg") . "\n");
503
	    unlock($voucherlck);
504
            return 1;
505
        }
506
        fwrite($fd, "{$config['voucher'][$cpzone]['rollbits']},{$config['voucher'][$cpzone]['ticketbits']},{$config['voucher'][$cpzone]['checksumbits']},{$config['voucher'][$cpzone]['magic']},{$config['voucher'][$cpzone]['charset']}\n");
507
        fclose($fd);
508
        @chmod("{$g['varetc_path']}/voucher_{$cpzone}.cfg", 0600);
509
	unlock($voucherlck);
510

    
511
        if (($g['booting'] || $sync == true) && is_array($config['voucher'][$cpzone]['roll'])) {
512

    
513
		$voucherlck = lock("voucher{$cpzone}", LOCK_EX);
514

    
515
            // create active and used DB per roll on ramdisk from config
516
            foreach ($config['voucher'][$cpzone]['roll'] as $rollent) {
517

    
518
                $roll = $rollent['number'];
519
                voucher_write_used_db($roll, $rollent['used']);
520
                $minutes = $rollent['minutes'];
521
                $active_vouchers = array();
522
                $a_active = &$rollent['active'];
523
                if (is_array($a_active)) {
524
                    foreach ($a_active as $activent) {
525
                        $voucher = $activent['voucher'];
526
                        $timestamp = $activent['timestamp'];
527
                        $minutes = $activent['minutes'];
528
                        // its tempting to check for expired timestamps, but during
529
                        // bootup, we most likely don't have the correct time time.
530
                        $active_vouchers[$voucher] = "$timestamp,$minutes";
531
                    }
532
                }
533
                voucher_write_active_db($roll, $active_vouchers);
534
            }
535

    
536
		unlock($voucherlck);
537
        }
538

    
539
	return 0;
540
}
541

    
542
/* write bitstring of used vouchers to ramdisk. 
543
 * Bitstring must already be base64_encoded!
544
 */
545
function voucher_write_used_db($roll, $vdb) {
546
	global $g, $cpzone;
547

    
548
	$fd = fopen("{$g['vardb_path']}/voucher_{$cpzone}_used_$roll.db", "w");
549
	if ($fd) {
550
		fwrite($fd, $vdb . "\n");
551
		fclose($fd);
552
	} else
553
		voucher_log(LOG_ERR, sprintf(gettext('cant write %1$s/voucher_%s_used_%2$s.db'), $g['vardb_path'], $cpzone, $roll));
554
}
555

    
556
/* return assoc array of active vouchers with activation timestamp
557
 * voucher is index. 
558
 */
559
function voucher_read_active_db($roll) {
560
	global $g, $cpzone;
561

    
562
	$active = array();
563
	$dirty = 0;
564
	$file = "{$g['vardb_path']}/voucher_{$cpzone}_active_$roll.db";
565
	if (file_exists($file)) {
566
		$fd = fopen($file, "r");
567
		if ($fd) {
568
			while (!feof($fd)) {
569
				$line = trim(fgets($fd));
570
				if ($line) {
571
					list($voucher,$timestamp,$minutes) = explode(",", $line); // voucher,timestamp
572
					if ((($timestamp + (60*$minutes)) - time()) > 0)
573
						$active[$voucher] = "$timestamp,$minutes";
574
					else
575
						$dirty=1;
576
				}
577
			}
578
			fclose($fd);
579
			if ($dirty) { // if we found expired entries, lets save our snapshot
580
				voucher_write_active_db($roll, $active);
581

    
582
				/* Triger a sync of the vouchers on config */
583
				send_event("service sync vouchers");
584
			}
585
		}
586
	}
587
	return $active;
588
}
589

    
590
/* store array of active vouchers back to DB */
591
function voucher_write_active_db($roll, $active) {
592
    global $g, $cpzone;
593

    
594
	if (!is_array($active))
595
		return;
596
    $fd = fopen("{$g['vardb_path']}/voucher_{$cpzone}_active_$roll.db", "w");
597
    if ($fd) {
598
        foreach($active as $voucher => $value)
599
            fwrite($fd, "$voucher,$value\n");
600
        fclose($fd);
601
    }
602
}
603

    
604
/* return how many vouchers are marked used on a roll */
605
function voucher_used_count($roll) {
606
    global $g, $cpzone;
607

    
608
    $bitstring = voucher_read_used_db($roll);
609
    $max = strlen($bitstring) * 8;
610
    $used = 0;
611
    for ($i = 1; $i <= $max; $i++) {
612
        // check if ticket already used or not. 
613
        $pos = $i >> 3;            // divide by 8 -> octet
614
        $mask = 1 << ($i % 8);  // mask to test bit in octet
615
        if (ord($bitstring[$pos]) & $mask)
616
            $used++;
617
    }   
618
    return $used;
619
}
620

    
621
function voucher_read_used_db($roll) {
622
    global $g, $cpzone;
623

    
624
    $vdb = "";
625
    $file = "{$g['vardb_path']}/voucher_{$cpzone}_used_$roll.db";
626
    if (file_exists($file)) {
627
        $fd = fopen($file, "r");
628
        if ($fd) {
629
            $vdb = trim(fgets($fd));
630
            fclose($fd);
631
        } else {
632
	    voucher_log(LOG_ERR, sprintf(gettext('cant read %1$s/voucher_%s_used_%2$s.db'), $g['vardb_path'], $cpzone, $roll));
633
        }
634
    }
635
    return base64_decode($vdb);
636
}
637

    
638
function voucher_unlink_db($roll) {
639
    global $g, $cpzone;
640
    @unlink("{$g['vardb_path']}/voucher_{$cpzone}_used_$roll.db");
641
    @unlink("{$g['vardb_path']}/voucher_{$cpzone}_active_$roll.db");
642
}
643

    
644
/* we share the log with captiveportal for now */
645
function voucher_log($priority, $message) {
646

    
647
    $message = trim($message);
648
    openlog("logportalauth", LOG_PID, LOG_LOCAL4);
649
    syslog($priority, sprintf(gettext("Voucher: %s"),$message));
650
    closelog();
651
}
652

    
653
/* Save active and used voucher DB into XML config and write it to flash
654
 * Called during reboot -> system_reboot_cleanup() and every active voucher change
655
 */
656
function voucher_save_db_to_config() {
657
    global $config, $g, $cpzone;
658

    
659
	if (is_array($config['voucher'])) {
660
		foreach ($config['voucher'] as $voucherzone => $vcfg) {
661
			$cpzone = $voucherzone;
662
			voucher_save_db_to_config_zone();
663
		}
664
	}
665
}
666

    
667
function voucher_save_db_to_config_zone() {
668
    global $config, $g, $cpzone;
669
    
670
    if (!isset($config['voucher'][$cpzone]['enable']))
671
        return;   // no vouchers or don't want to save DB's
672

    
673
    if (!is_array($config['voucher'][$cpzone]['roll']))
674
	return;
675

    
676
    $voucherlck = lock("voucher{$cpzone}", LOCK_EX);
677

    
678
    // walk all active rolls and save runtime DB's to flash
679
    $a_roll = &$config['voucher'][$cpzone]['roll'];
680
    while (list($key, $value) = each($a_roll)) {
681
        $rollent = &$a_roll[$key];
682
        $roll = $rollent['number'];
683
        $bitmask = voucher_read_used_db($roll);
684
        $rollent['used'] = base64_encode($bitmask);
685
        $active_vouchers = voucher_read_active_db($roll);
686
        $db = array();
687
		$dbi = 1;
688
        foreach($active_vouchers as $voucher => $line) {
689
            list($timestamp,$minutes) = explode(",", $line);
690
            $activent['voucher'] = $voucher;
691
            $activent['timestamp'] = $timestamp;
692
            $activent['minutes'] = $minutes;
693
            $db["v{$dbi}"] = $activent;
694
	    $dbi++;
695
        }
696
        $rollent['active'] = $db;
697
    }
698

    
699
    unlock($voucherlck);
700

    
701
    write_config("Synching vouchers");
702
    return;
703
}
704

    
705
?>
(57-57/67)