Project

General

Profile

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

    
29
/* include all configuration functions */
30
if (!function_exists('captiveportal_syslog')) {
31
	require_once("captiveportal.inc");
32
}
33

    
34
function xmlrpc_sync_voucher_details(&$syncip, &$port, &$username, &$password) {
35
	global $config;
36

    
37
	if (!empty($config['voucher'][$cpzone]['vouchersyncdbip'])) {
38
		$syncip = $config['voucher'][$cpzone]['vouchersyncdbip'];
39
		$port = $config['voucher'][$cpzone]['vouchersyncport'];
40
		$username = $config['voucher'][$cpzone]['vouchersyncusername'];
41
		$password = $config['voucher'][$cpzone]['vouchersyncpass'];
42
		return true;
43
	}
44

    
45
	if (!is_array($config['hasync']) ||
46
	    empty($config['hasync']['synchronizetoip']) ||
47
	    $config['hasync']['synchronizecaptiveportal'] == "") {
48
		return false;
49
	}
50

    
51
	$syncip = $config['hasync']['synchronizetoip'];
52
	$password = $config['hasync']['password'];
53
	if (empty($config['hasync']['username'])) {
54
		$username = "admin";
55
	} else {
56
		$username = $config['hasync']['username'];
57
	}
58

    
59
	/* if port is empty lets rely on the protocol selection */
60
	$port = $config['system']['webgui']['port'];
61
	if (empty($port)) {
62
		if ($config['system']['webgui']['protocol'] == "http") {
63
			$port = "80";
64
		} else {
65
			$port = "443";
66
		}
67
	}
68

    
69
	return true;
70
}
71

    
72
function xmlrpc_sync_voucher_expire($vouchers, $syncip, $port, $password, $username) {
73
	global $cpzone;
74
	require_once("xmlrpc_client.inc");
75

    
76
	/* Construct code that is run on remote machine */
77
	$execcmd = <<<EOF
78
	global \$cpzone;
79
	require_once('/etc/inc/captiveportal.inc');
80
	require_once('/etc/inc/voucher.inc');
81
	\$cpzone = "$cpzone";
82
	return voucher_expire("$vouchers");
83

    
84
EOF;
85
	$rpc_client = new pfsense_xmlrpc_client();
86
	$rpc_client->setConnectionData($syncip, $port, $username, $password);
87
	$resp = $rpc_client->xmlrpc_exec_php($execcmd);
88
	if (empty($resp)) {
89
		return false;
90
	}
91
	return $resp;
92
}
93

    
94
function xmlrpc_sync_voucher_disconnect($dbent, $syncip, $port, $password, $username, $term_cause = 1, $stop_time = null) {
95
	global $cpzone;
96
	require_once("xmlrpc_client.inc");
97
	/* Construct code that is run on remote machine */
98
	$dbent_str = addslashes(serialize($dbent));
99
	$tmp_stop_time = (isset($stop_time)) ? $stop_time : "null";
100
	$execcmd = <<<EOF
101
	global \$cpzone;
102
	require_once('/etc/inc/captiveportal.inc');
103
	require_once('/etc/inc/voucher.inc');
104
	\$cpzone = "$cpzone";
105
	\$dbent = unserialize("$dbent_str");
106
	return captiveportal_disconnect(\$dbent, $term_cause, $tmp_stop_time);
107

    
108
EOF;
109
	$rpc_client = new pfsense_xmlrpc_client();
110
	$rpc_client->setConnectionData($syncip, $port, $username, $password);
111
	$resp = $rpc_client->xmlrpc_exec_php($execcmd);
112
	if (empty($resp)) {
113
		return false;
114
	}
115
	return $resp;
116
}
117

    
118
function xmlrpc_sync_used_voucher($voucher_received, $syncip, $port, $password, $username) {
119
	global $config, $cpzone;
120
	require_once("xmlrpc_client.inc");
121

    
122
	/* Construct code that is run on remote machine */
123
	$execcmd = <<<EOF
124
	global \$cpzone, \$config;
125
	require_once('/etc/inc/voucher.inc');
126
	\$cpzone = "$cpzone";
127
	\$timeleft = voucher_auth("$voucher_received");
128
	\$toreturn = array();
129
	\$toreturn['timeleft'] = \$timeleft;
130
	\$toreturn['voucher'] = array();
131
	if (is_array(\$config['voucher'][\$cpzone]['roll'])) {
132
		\$toreturn['voucher']['roll'] = \$config['voucher'][\$cpzone]['roll'];
133
	}
134

    
135
EOF;
136
	$rpc_client = new pfsense_xmlrpc_client();
137
	$rpc_client->setConnectionData($syncip, $port, $username, $password);
138
	$resp = $rpc_client->xmlrpc_exec_php($execcmd);
139

    
140
	if (!is_array($config['voucher'])) {
141
		$config['voucher'] = array();
142
	}
143

    
144
	if (is_array($resp['voucher']['roll'])) {
145
		$config['voucher'][$cpzone]['roll'] = $resp['voucher']['roll'];
146
		write_config(sprintf(gettext('Captive Portal Voucher database synchronized with %1$s:%2$s'), $syncip, $port));
147
		voucher_configure_zone(true);
148
		unset($resp['voucher']);
149
	} else if (!isset($resp['timeleft'])) {
150
		return 0;
151
	}
152

    
153
	return $resp['timeleft'];
154
}
155

    
156
function voucher_expire($voucher_received) {
157
	global $g, $config, $cpzone, $cpzoneid;
158

    
159
	// XMLRPC Call over to the master Voucher node
160
	if (xmlrpc_sync_voucher_details($syncip, $syncport,
161
	    $vouchersyncusername, $syncpass)) {
162
		xmlrpc_sync_voucher_expire($voucher_received, $syncip,
163
		    $syncport, $syncpass, $vouchersyncusername);
164
	}
165

    
166
	$voucherlck = lock("voucher{$cpzone}", LOCK_EX);
167

    
168
	// read rolls into assoc array with rollid as key and minutes as value
169
	$tickets_per_roll = array();
170
	$minutes_per_roll = array();
171
	if (is_array($config['voucher'][$cpzone]['roll'])) {
172
		foreach ($config['voucher'][$cpzone]['roll'] as $rollent) {
173
			$tickets_per_roll[$rollent['number']] = $rollent['count'];
174
			$minutes_per_roll[$rollent['number']] = $rollent['minutes'];
175
		}
176
	}
177

    
178
	// split into an array. Useful for multiple vouchers given
179
	$a_vouchers_received = preg_split("/[\t\n\r ]+/s", $voucher_received);
180
	$active_dirty = false;
181
	$unsetindexes = array();
182

    
183
	// go through all received vouchers, check their valid and extract
184
	// Roll# and Ticket# using the external readvoucher binary
185
	foreach ($a_vouchers_received as $voucher) {
186
		$v = escapeshellarg($voucher);
187
		if (strlen($voucher) < 5) {
188
			captiveportal_syslog("${voucher} invalid: Too short!");
189
			continue;   // seems too short to be a voucher!
190
		}
191

    
192
		unset($output);
193
		$_gb = exec("/usr/local/bin/voucher -c {$g['varetc_path']}/voucher_{$cpzone}.cfg -k {$g['varetc_path']}/voucher_{$cpzone}.public -- $v", $output);
194
		list($status, $roll, $nr) = explode(" ", $output[0]);
195
		if ($status == "OK") {
196
			// check if we have this ticket on a registered roll for this ticket
197
			if ($tickets_per_roll[$roll] && ($nr <= $tickets_per_roll[$roll])) {
198
				// voucher is from a registered roll.
199
				if (!isset($active_vouchers[$roll])) {
200
					$active_vouchers[$roll] = voucher_read_active_db($roll);
201
				}
202
				// valid voucher. Store roll# and ticket#
203
				if (!empty($active_vouchers[$roll][$voucher])) {
204
					$active_dirty = true;
205
					unset($active_vouchers[$roll][$voucher]);
206
				}
207
				// check if voucher already marked as used
208
				if (!isset($bitstring[$roll])) {
209
					$bitstring[$roll] = voucher_read_used_db($roll);
210
				}
211
				$pos = $nr >> 3; // divide by 8 -> octet
212
				$mask = 1 << ($nr % 8);
213
				// mark bit for this voucher as used
214
				if (!(ord($bitstring[$roll][$pos]) & $mask)) {
215
					$bitstring[$roll][$pos] = chr(ord($bitstring[$roll][$pos]) | $mask);
216
				}
217
				captiveportal_syslog("{$voucher} ({$roll}/{$nr}) forced to expire");
218

    
219
				/* Check if this voucher has any active sessions */
220
				$cpentry = captiveportal_read_db("WHERE username = '{$voucher}'");
221
				if (!empty($cpentry) && !empty($cpentry[0])) {
222
					if (empty($cpzoneid) && !empty($config['captiveportal'][$cpzone])) {
223
						$cpzoneid = $config['captiveportal'][$cpzone]['zoneid'];
224
					}
225
					$cpentry = $cpentry[0];
226
					captiveportal_disconnect($cpentry, 13);
227
					captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "FORCLY TERMINATING VOUCHER {$voucher} SESSION");
228
					$unsetindexes[] = $cpentry[5];
229
				}
230
			} else {
231
				captiveportal_syslog(sprintf(gettext('%1$s (%2$s/%3$s): not found on any registered Roll'), $voucher, $roll, $nr));
232
			}
233
		} else {
234
			// hmm, thats weird ... not what I expected
235
			captiveportal_syslog(sprintf(gettext('%1$s invalid: %2$s!!'), $voucher, $output[0]));
236
		}
237
	}
238

    
239
	// Refresh active DBs
240
	if ($active_dirty == true) {
241
		foreach ($active_vouchers as $roll => $active) {
242
			voucher_write_active_db($roll, $active);
243
		}
244
		unset($active_vouchers);
245

    
246
		/* Trigger a sync of the vouchers on config */
247
		send_event("service sync vouchers");
248
	}
249

    
250
	// Write back the used DB's
251
	if (is_array($bitstring)) {
252
		foreach ($bitstring as $roll => $used) {
253
			if (is_array($used)) {
254
				foreach ($used as $u) {
255
					voucher_write_used_db($roll, base64_encode($u));
256
				}
257
			} else {
258
				voucher_write_used_db($roll, base64_encode($used));
259
			}
260
		}
261
		unset($bitstring);
262
	}
263

    
264
	unlock($voucherlck);
265

    
266
	/* Write database */
267
	if (!empty($unsetindexes)) {
268
		captiveportal_remove_entries($unsetindexes);
269
	}
270

    
271
	return true;
272
}
273

    
274
/*
275
 * Authenticate a voucher and return the remaining time credit in minutes
276
 * if $test is set, don't mark the voucher as used nor add it to the list
277
 * of active vouchers
278
 * If $test is set, simply test the voucher. Don't change anything
279
 * but return a more verbose error and result message back
280
 */
281
function voucher_auth($voucher_received, $test = 0) {
282
	global $g, $config, $cpzone, $dbc;
283

    
284
	if (!isset($config['voucher'][$cpzone]['enable'])) {
285
		return 0;
286
	}
287

    
288
	// XMLRPC Call over to the master Voucher node
289
	if (xmlrpc_sync_voucher_details($syncip, $syncport,
290
	    $vouchersyncusername, $syncpass)) {
291
		$remote_time_used = xmlrpc_sync_used_voucher($voucher_received,
292
		    $syncip, $syncport, $syncpass, $vouchersyncusername);
293
	}
294

    
295
	$voucherlck = lock("voucher{$cpzone}", LOCK_EX);
296

    
297
	// read rolls into assoc array with rollid as key and minutes as value
298
	$tickets_per_roll = array();
299
	$minutes_per_roll = array();
300
	if (is_array($config['voucher'][$cpzone]['roll'])) {
301
		foreach ($config['voucher'][$cpzone]['roll'] as $rollent) {
302
			$tickets_per_roll[$rollent['number']] = $rollent['count'];
303
			$minutes_per_roll[$rollent['number']] = $rollent['minutes'];
304
		}
305
	}
306

    
307
	// split into an array. Useful for multiple vouchers given
308
	$a_vouchers_received = preg_split("/[\t\n\r ]+/s", $voucher_received);
309
	$error = 0;
310
	$test_result = array();     // used to display for voucher test option in GUI
311
	$total_minutes = 0;
312
	$first_voucher = "";
313
	$first_voucher_roll = 0;
314

    
315
	// go through all received vouchers, check their valid and extract
316
	// Roll# and Ticket# using the external readvoucher binary
317
	foreach ($a_vouchers_received as $voucher) {
318
		$v = escapeshellarg($voucher);
319
		if (strlen($voucher) < 5) {
320
			$voucher_err_text = sprintf(gettext("%s invalid: Too short!"), $voucher);
321
			$test_result[] = $voucher_err_text;
322
			captiveportal_syslog($voucher_err_text);
323
			$error++;
324
			continue;   // seems too short to be a voucher!
325
		}
326

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

    
384
	// if this was a test call, we're done. Return the result.
385
	if ($test) {
386
		if ($error) {
387
			$test_result[] = gettext("Access denied!");
388
		} else {
389
			$test_result[] = sprintf(gettext("Access granted for %d Minutes in total."), $total_minutes);
390
		}
391
		unlock($voucherlck);
392

    
393
		return $test_result;
394
	}
395

    
396
	// if we had an error (one of the vouchers is invalid), return 0.
397
	// Discussion: we could return the time remaining for good vouchers, but then
398
	// the user wouldn't know that he used at least one invalid voucher.
399
	if ($error) {
400
		unlock($voucherlck);
401
		if ($total_minutes > 0) {   // probably not needed, but want to make sure
402
			$total_minutes = 0;     // we only report -1 (expired) or 0 (no access)
403
		}
404
		return $total_minutes;       // well, at least one voucher had errors. Say NO ACCESS
405
	}
406

    
407
	// If we did a XMLRPC sync earlier check the timeleft
408
	if (!empty($config['voucher'][$cpzone]['vouchersyncdbip'])) {
409
		if (!is_null($remote_time_used)) {
410
			$total_minutes = $remote_time_used;
411
		} else if ($remote_time_used < $total_minutes) {
412
			$total_minutes -= $remote_time_used;
413
		}
414
	}
415

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

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

    
441
	$active_vouchers[$first_voucher_roll][$first_voucher] = "$timestamp,$minutes";
442
	voucher_write_active_db($first_voucher_roll, $active_vouchers[$first_voucher_roll]);
443

    
444
	/* Trigger a sync of the vouchers on config */
445
	send_event("service sync vouchers");
446

    
447
	unlock($voucherlck);
448

    
449
	return $total_minutes;
450
}
451

    
452
function voucher_configure($sync = false) {
453
	global $config, $g, $cpzone;
454

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

    
473
function voucher_configure_zone($sync = false) {
474
	global $config, $g, $cpzone;
475

    
476
	if (!isset($config['voucher'][$cpzone]['enable'])) {
477
		return 0;
478
	}
479

    
480
	if ($sync == true) {
481
		captiveportal_syslog("Writing voucher db from sync data...");
482
	}
483

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

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

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

    
510
	if ((platform_booting() || $sync == true) && is_array($config['voucher'][$cpzone]['roll'])) {
511

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

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

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

    
539
		unlock($voucherlck);
540
	}
541

    
542
	return 0;
543
}
544

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

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

    
560
/* return assoc array of active vouchers with activation timestamp
561
 * voucher is index.
562
 */
563
function voucher_read_active_db($roll) {
564
	global $g, $cpzone;
565

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

    
587
				/* Trigger a sync of the vouchers on config */
588
				send_event("service sync vouchers");
589
			}
590
		}
591
	}
592
	return $active;
593
}
594

    
595
/* store array of active vouchers back to DB */
596
function voucher_write_active_db($roll, $active) {
597
	global $g, $cpzone;
598

    
599
	if (!is_array($active)) {
600
		return;
601
	}
602
	$fd = fopen("{$g['vardb_path']}/voucher_{$cpzone}_active_$roll.db", "w");
603
	if ($fd) {
604
		foreach ($active as $voucher => $value) {
605
			fwrite($fd, "$voucher,$value\n");
606
		}
607
		fclose($fd);
608
	}
609
}
610

    
611
/* return how many vouchers are marked used on a roll */
612
function voucher_used_count($roll) {
613
	global $g, $cpzone;
614

    
615
	$bitstring = voucher_read_used_db($roll);
616
	$max = strlen($bitstring) * 8;
617
	$used = 0;
618
	for ($i = 1; $i <= $max; $i++) {
619
		// check if ticket already used or not.
620
		$pos = $i >> 3;            // divide by 8 -> octet
621
		$mask = 1 << ($i % 8);  // mask to test bit in octet
622
		if (ord($bitstring[$pos]) & $mask) {
623
			$used++;
624
		}
625
	}
626
	unset($bitstring);
627

    
628
	return $used;
629
}
630

    
631
function voucher_read_used_db($roll) {
632
	global $g, $cpzone;
633

    
634
	$vdb = "";
635
	$file = "{$g['vardb_path']}/voucher_{$cpzone}_used_$roll.db";
636
	if (file_exists($file)) {
637
		$fd = fopen($file, "r");
638
		if ($fd) {
639
			$vdb = trim(fgets($fd));
640
			fclose($fd);
641
		} else {
642
			voucher_log(LOG_ERR, sprintf(gettext('cant read %1$s/voucher_%2$s_used_%3$s.db'), $g['vardb_path'], $cpzone, $roll));
643
		}
644
	}
645
	return base64_decode($vdb);
646
}
647

    
648
function voucher_unlink_db($roll) {
649
	global $g, $cpzone;
650
	@unlink("{$g['vardb_path']}/voucher_{$cpzone}_used_$roll.db");
651
	@unlink("{$g['vardb_path']}/voucher_{$cpzone}_active_$roll.db");
652
}
653

    
654
/* we share the log with captiveportal for now */
655
function voucher_log($priority, $message) {
656

    
657
	$message = trim($message);
658
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
659
	syslog($priority, sprintf(gettext("Voucher: %s"), $message));
660
	closelog();
661
}
662

    
663
/* Save active and used voucher DB into XML config and write it to flash
664
 * Called during reboot -> system_reboot_cleanup() and every active voucher change
665
 */
666
function voucher_save_db_to_config() {
667
	global $config, $g, $cpzone;
668

    
669
	if (is_array($config['voucher'])) {
670
		foreach ($config['voucher'] as $voucherzone => $vcfg) {
671
			$cpzone = $voucherzone;
672
			voucher_save_db_to_config_zone();
673
		}
674
	}
675
}
676

    
677
function voucher_save_db_to_config_zone() {
678
	global $config, $g, $cpzone;
679

    
680
	if (!isset($config['voucher'][$cpzone]['enable'])) {
681
		return;   // no vouchers or don't want to save DB's
682
	}
683

    
684
	if (!is_array($config['voucher'][$cpzone]['roll'])) {
685
		return;
686
	}
687

    
688
	$voucherlck = lock("voucher{$cpzone}", LOCK_EX);
689

    
690
	// walk all active rolls and save runtime DB's to flash
691
	$a_roll = &$config['voucher'][$cpzone]['roll'];
692
	while (list($key, $value) = each($a_roll)) {
693
		$rollent = &$a_roll[$key];
694
		$roll = $rollent['number'];
695
		$bitmask = voucher_read_used_db($roll);
696
		$rollent['used'] = base64_encode($bitmask);
697
		$active_vouchers = voucher_read_active_db($roll);
698
		$db = array();
699
		$dbi = 1;
700
		foreach ($active_vouchers as $voucher => $line) {
701
			list($timestamp, $minutes) = explode(",", $line);
702
			$activent['voucher'] = $voucher;
703
			$activent['timestamp'] = $timestamp;
704
			$activent['minutes'] = $minutes;
705
			$db["v{$dbi}"] = $activent;
706
			$dbi++;
707
		}
708
		$rollent['active'] = $db;
709
		unset($active_vouchers);
710
	}
711

    
712
	unlock($voucherlck);
713

    
714
	write_config(gettext("Syncing vouchers"));
715
	return;
716
}
717

    
718
?>
(54-54/60)