Project

General

Profile

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

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

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

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

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

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

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

    
67
	return true;
68
}
69

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

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

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

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

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

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

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

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

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

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

    
151
	return $resp['timeleft'];
152
}
153

    
154
function voucher_expire($voucher_received) {
155
	global $g, $config, $cpzone, $cpzoneid;
156

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

    
164
	$voucherlck = lock("voucher{$cpzone}", LOCK_EX);
165

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

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

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

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

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

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

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

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

    
262
	unlock($voucherlck);
263

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

    
269
	return true;
270
}
271

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

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

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

    
293
	$voucherlck = lock("voucher{$cpzone}", LOCK_EX);
294

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

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

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

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

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

    
391
		return $test_result;
392
	}
393

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

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

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

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

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

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

    
445
	unlock($voucherlck);
446

    
447
	return $total_minutes;
448
}
449

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

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

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

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

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

    
482
	$voucherlck = lock("voucher{$cpzone}", LOCK_EX);
483

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

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

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

    
510
		$voucherlck = lock("voucher{$cpzone}", LOCK_EX);
511

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

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

    
537
		unlock($voucherlck);
538
	}
539

    
540
	return 0;
541
}
542

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

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

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

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

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

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

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

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

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

    
626
	return $used;
627
}
628

    
629
function voucher_read_used_db($roll) {
630
	global $g, $cpzone;
631

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

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

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

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

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

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

    
675
function voucher_save_db_to_config_zone() {
676
	global $config, $g, $cpzone;
677

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

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

    
686
	$voucherlck = lock("voucher{$cpzone}", LOCK_EX);
687

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

    
710
	unlock($voucherlck);
711

    
712
	write_config(gettext("Syncing vouchers"));
713
	return;
714
}
715

    
716
?>
(53-53/60)