Project

General

Profile

Download (25.8 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php
2
/*
3
	voucher.inc
4
	Copyright (C) 2010-2012 Ermal Luçi <eri@pfsense.org>
5
	Copyright (C) 2010 Scott Ullrich <sullrich@gmail.com>
6
	Copyright (C) 2007 Marcel Wiget <mwiget@mac.com>
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

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

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

    
42
function xmlrpc_sync_voucher_expire($vouchers, $syncip, $port, $password, $username) {
43
	global $g, $config, $cpzone;
44
	require_once("xmlrpc.inc");
45

    
46
	$protocol = "http";
47
	if (is_array($config['system']) && is_array($config['system']['webgui']) && !empty($config['system']['webgui']['protocol']) &&
48
	    $config['system']['webgui']['protocol'] == "https") {
49
		$protocol = "https";
50
	}
51
	if ($protocol == "https" || $port == "443") {
52
		$url = "https://{$syncip}";
53
	} else {
54
		$url = "http://{$syncip}";
55
	}
56

    
57
	/* Construct code that is run on remote machine */
58
	$method = 'pfsense.exec_php';
59
	$execcmd = <<<EOF
60
	global \$cpzone;
61
	require_once('/etc/inc/captiveportal.inc');
62
	require_once('/etc/inc/voucher.inc');
63
	\$cpzone = "$cpzone";
64
	voucher_expire("$vouchers");
65

    
66
EOF;
67

    
68
	/* assemble xmlrpc payload */
69
	$params = array(
70
		XML_RPC_encode($password),
71
		XML_RPC_encode($execcmd)
72
	);
73

    
74
	log_error("Captive Portal Voucher XMLRPC sync data {$url}:{$port}.");
75
	$msg = new XML_RPC_Message($method, $params);
76
	$cli = new XML_RPC_Client('/xmlrpc.php', $url, $port);
77
	$cli->setCredentials($username, $password);
78
	$resp = $cli->send($msg, "250");
79
	if (!is_object($resp)) {
80
		$error = "A communications error occurred while attempting CaptivePortalVoucherSync XMLRPC sync with {$url}:{$port} (pfsense.exec_php).";
81
		log_error($error);
82
		file_notice("CaptivePortalVoucherSync", $error, "Communications error occurred", "");
83
		return false;
84
	} elseif ($resp->faultCode()) {
85
		$error = "An error code was received while attempting CaptivePortalVoucherSync XMLRPC sync with {$url}:{$port} - Code " . $resp->faultCode() . ": " . $resp->faultString();
86
		log_error($error);
87
		file_notice("CaptivePortalVoucherSync", $error, "Error code received", "");
88
		return false;
89
	} else {
90
		log_error("CaptivePortalVoucherSync XMLRPC reload data success with {$url}:{$port} (pfsense.exec_php).");
91
	}
92

    
93
	$toreturn = XML_RPC_Decode($resp->value());
94

    
95
	return $toreturn;
96
}
97

    
98
function xmlrpc_sync_voucher_disconnect($dbent, $syncip, $port, $password, $username, $term_cause = 1, $stop_time = null) {
99
	global $g, $config, $cpzone;
100
	require_once("xmlrpc.inc");
101

    
102
	$protocol = "http";
103
	if (is_array($config['system']) && is_array($config['system']['webgui']) && !empty($config['system']['webgui']['protocol']) &&
104
	    $config['system']['webgui']['protocol'] == "https") {
105
		$protocol = "https";
106
	}
107
	if ($protocol == "https" || $port == "443") {
108
		$url = "https://{$syncip}";
109
	} else {
110
		$url = "http://{$syncip}";
111
	}
112

    
113
	/* Construct code that is run on remote machine */
114
	$dbent_str = serialize($dbent);
115
	$tmp_stop_time = (isset($stop_time)) ? $stop_time : "null";
116
	$method = 'pfsense.exec_php';
117
	$execcmd = <<<EOF
118
	global \$cpzone;
119
	require_once('/etc/inc/captiveportal.inc');
120
	require_once('/etc/inc/voucher.inc');
121
	\$cpzone = "$cpzone";
122
	\$radiusservers = captiveportal_get_radius_servers();
123
	\$dbent = unserialize("$dbent_str");
124
	captiveportal_disconnect(\$dbent, \$radiusservers, $term_cause, $tmp_stop_time);
125

    
126
EOF;
127

    
128
	/* assemble xmlrpc payload */
129
	$params = array(
130
		XML_RPC_encode($password),
131
		XML_RPC_encode($execcmd)
132
	);
133

    
134
	log_error("Captive Portal Voucher XMLRPC sync data {$url}:{$port}.");
135
	$msg = new XML_RPC_Message($method, $params);
136
	$cli = new XML_RPC_Client('/xmlrpc.php', $url, $port);
137
	$cli->setCredentials($username, $password);
138
	$resp = $cli->send($msg, "250");
139
	if (!is_object($resp)) {
140
		$error = "A communications error occurred while attempting CaptivePortalVoucherSync XMLRPC sync with {$url}:{$port} (pfsense.exec_php).";
141
		log_error($error);
142
		file_notice("CaptivePortalVoucherSync", $error, "Communications error occurred", "");
143
		return false;
144
	} elseif ($resp->faultCode()) {
145
		$error = "An error code was received while attempting CaptivePortalVoucherSync XMLRPC sync with {$url}:{$port} - Code " . $resp->faultCode() . ": " . $resp->faultString();
146
		log_error($error);
147
		file_notice("CaptivePortalVoucherSync", $error, "Error code received", "");
148
		return false;
149
	} else {
150
		log_error("CaptivePortalVoucherSync XMLRPC reload data success with {$url}:{$port} (pfsense.exec_php).");
151
	}
152

    
153
	$toreturn = XML_RPC_Decode($resp->value());
154

    
155
	return $toreturn;
156
}
157

    
158
function xmlrpc_sync_used_voucher($voucher_received, $syncip, $port, $password, $username) {
159
	global $g, $config, $cpzone;
160
	require_once("xmlrpc.inc");
161

    
162
	$protocol = "http";
163
	if (is_array($config['system']) && is_array($config['system']['webgui']) && !empty($config['system']['webgui']['protocol']) &&
164
	    $config['system']['webgui']['protocol'] == "https") {
165
		$protocol = "https";
166
	}
167
	if ($protocol == "https" || $port == "443") {
168
		$url = "https://{$syncip}";
169
	} else {
170
		$url = "http://{$syncip}";
171
	}
172

    
173
	/* Construct code that is run on remote machine */
174
	$method = 'pfsense.exec_php';
175
	$execcmd = <<<EOF
176
	global \$cpzone;
177
	require_once('/etc/inc/voucher.inc');
178
	\$cpzone = "$cpzone";
179
	\$timeleft = voucher_auth("$voucher_received");
180
	\$toreturn = array();
181
	\$toreturn['timeleft'] = \$timeleft;
182
	\$toreturn['voucher'] = array();
183
	\$toreturn['voucher']['roll'] = \$config['voucher'][\$cpzone]['roll'];
184

    
185
EOF;
186

    
187
	/* assemble xmlrpc payload */
188
	$params = array(
189
		XML_RPC_encode($password),
190
		XML_RPC_encode($execcmd)
191
	);
192

    
193
	log_error("Captive Portal Voucher XMLRPC sync data {$url}:{$port}.");
194
	$msg = new XML_RPC_Message($method, $params);
195
	$cli = new XML_RPC_Client('/xmlrpc.php', $url, $port);
196
	$cli->setCredentials($username, $password);
197
	$resp = $cli->send($msg, "250");
198
	if (!is_object($resp)) {
199
		$error = "A communications error occurred while attempting CaptivePortalVoucherSync XMLRPC sync with {$url}:{$port} (pfsense.exec_php).";
200
		log_error($error);
201
		file_notice("CaptivePortalVoucherSync", $error, "Communications error occurred", "");
202
		return null; // $timeleft
203
	} elseif ($resp->faultCode()) {
204
		$error = "An error code was received while attempting CaptivePortalVoucherSync XMLRPC sync with {$url}:{$port} - Code " . $resp->faultCode() . ": " . $resp->faultString();
205
		log_error($error);
206
		file_notice("CaptivePortalVoucherSync", $error, "Error code received", "");
207
		return null; // $timeleft
208
	} else {
209
		log_error("CaptivePortalVoucherSync XMLRPC reload data success with {$url}:{$port} (pfsense.exec_php).");
210
	}
211
	$toreturn = XML_RPC_Decode($resp->value());
212
	if (!is_array($config['voucher'])) {
213
		$config['voucher'] = array();
214
	}
215

    
216
	if (is_array($toreturn['voucher']) && is_array($toreturn['voucher']['roll'])) {
217
		$config['voucher'][$cpzone]['roll'] = $toreturn['voucher']['roll'];
218
		write_config("Captive Portal Voucher database synchronized with {$url}");
219
		voucher_configure_zone(true);
220
		unset($toreturn['voucher']);
221
	} else if (!isset($toreturn['timeleft'])) {
222
		return null;
223
	}
224

    
225
	return $toreturn['timeleft'];
226
}
227

    
228
function voucher_expire($voucher_received) {
229
	global $g, $config, $cpzone, $cpzoneid;
230

    
231
	// XMLRPC Call over to the master Voucher node
232
	if (!empty($config['voucher'][$cpzone]['vouchersyncdbip'])) {
233
		$syncip   = $config['voucher'][$cpzone]['vouchersyncdbip'];
234
		$syncport = $config['voucher'][$cpzone]['vouchersyncport'];
235
		$syncpass = $config['voucher'][$cpzone]['vouchersyncpass'];
236
		$vouchersyncusername = $config['voucher'][$cpzone]['vouchersyncusername'];
237
		xmlrpc_sync_voucher_expire($voucher_received, $syncip, $syncport, $syncpass, $vouchersyncusername);
238
	}
239

    
240
	$voucherlck = lock("voucher{$cpzone}", LOCK_EX);
241

    
242
	// read rolls into assoc array with rollid as key and minutes as value
243
	$tickets_per_roll = array();
244
	$minutes_per_roll = array();
245
	if (is_array($config['voucher'][$cpzone]['roll'])) {
246
		foreach ($config['voucher'][$cpzone]['roll'] as $rollent) {
247
			$tickets_per_roll[$rollent['number']] = $rollent['count'];
248
			$minutes_per_roll[$rollent['number']] = $rollent['minutes'];
249
		}
250
	}
251

    
252
	// split into an array. Useful for multiple vouchers given
253
	$a_vouchers_received = preg_split("/[\t\n\r ]+/s", $voucher_received);
254
	$active_dirty = false;
255
	$unsetindexes = array();
256

    
257
	// go through all received vouchers, check their valid and extract
258
	// Roll# and Ticket# using the external readvoucher binary
259
	foreach ($a_vouchers_received as $voucher) {
260
		$v = escapeshellarg($voucher);
261
		if (strlen($voucher) < 3) {
262
			continue;   // seems too short to be a voucher!
263
		}
264

    
265
		unset($output);
266
		$_gb = exec("/usr/local/bin/voucher -c {$g['varetc_path']}/voucher_{$cpzone}.cfg -k {$g['varetc_path']}/voucher_{$cpzone}.public -- $v", $output);
267
		list($status, $roll, $nr) = explode(" ", $output[0]);
268
		if ($status == "OK") {
269
			// check if we have this ticket on a registered roll for this ticket
270
			if ($tickets_per_roll[$roll] && ($nr <= $tickets_per_roll[$roll])) {
271
				// voucher is from a registered roll.
272
				if (!isset($active_vouchers[$roll])) {
273
					$active_vouchers[$roll] = voucher_read_active_db($roll);
274
				}
275
				// valid voucher. Store roll# and ticket#
276
				if (!empty($active_vouchers[$roll][$voucher])) {
277
					$active_dirty = true;
278
					unset($active_vouchers[$roll][$voucher]);
279
				}
280
				// check if voucher already marked as used
281
				if (!isset($bitstring[$roll])) {
282
					$bitstring[$roll] = voucher_read_used_db($roll);
283
				}
284
				$pos = $nr >> 3; // divide by 8 -> octet
285
				$mask = 1 << ($nr % 8);
286
				// mark bit for this voucher as used
287
				if (!(ord($bitstring[$roll][$pos]) & $mask)) {
288
					$bitstring[$roll][$pos] = chr(ord($bitstring[$roll][$pos]) | $mask);
289
				}
290
				captiveportal_syslog("{$voucher} ({$roll}/{$nr}) forced to expire");
291

    
292
				/* Check if this voucher has any active sessions */
293
				$cpentry = captiveportal_read_db("WHERE username = '{$voucher}'");
294
				if (!empty($cpentry) && !empty($cpentry[0])) {
295
					if (empty($cpzoneid) && !empty($config['captiveportal'][$cpzone])) {
296
						$cpzoneid = $config['captiveportal'][$cpzone]['zoneid'];
297
					}
298
					$cpentry = $cpentry[0];
299
					captiveportal_disconnect($cpentry, null, 13);
300
					captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "FORCLY TERMINATING VOUCHER {$voucher} SESSION");
301
					$unsetindexes[] = $cpentry[5];
302
				}
303
			} else {
304
				captiveportal_syslog("$voucher ($roll/$nr): not found on any registered Roll");
305
			}
306
		} else {
307
			// hmm, thats weird ... not what I expected
308
			captiveportal_syslog("$voucher invalid: {$output[0]}!!");
309
		}
310
	}
311

    
312
	// Refresh active DBs
313
	if ($active_dirty == true) {
314
		foreach ($active_vouchers as $roll => $active) {
315
			voucher_write_active_db($roll, $active);
316
		}
317
		unset($active_vouchers);
318

    
319
		/* Trigger a sync of the vouchers on config */
320
		send_event("service sync vouchers");
321
	}
322

    
323
	// Write back the used DB's
324
	if (is_array($bitstring)) {
325
		foreach ($bitstring as $roll => $used) {
326
			if (is_array($used)) {
327
				foreach ($used as $u) {
328
					voucher_write_used_db($roll, base64_encode($u));
329
				}
330
			} else {
331
				voucher_write_used_db($roll, base64_encode($used));
332
			}
333
		}
334
		unset($bitstring);
335
	}
336

    
337
	unlock($voucherlck);
338

    
339
	/* Write database */
340
	if (!empty($unsetindexes)) {
341
		captiveportal_remove_entries($unsetindexes);
342
	}
343

    
344
	return true;
345
}
346

    
347
/*
348
 * Authenticate a voucher and return the remaining time credit in minutes
349
 * if $test is set, don't mark the voucher as used nor add it to the list
350
 * of active vouchers
351
 * If $test is set, simply test the voucher. Don't change anything
352
 * but return a more verbose error and result message back
353
 */
354
function voucher_auth($voucher_received, $test = 0) {
355
	global $g, $config, $cpzone, $dbc;
356

    
357
	if (!isset($config['voucher'][$cpzone]['enable'])) {
358
		return 0;
359
	}
360

    
361
	// XMLRPC Call over to the master Voucher node
362
	if (!empty($config['voucher'][$cpzone]['vouchersyncdbip'])) {
363
		$syncip   = $config['voucher'][$cpzone]['vouchersyncdbip'];
364
		$syncport = $config['voucher'][$cpzone]['vouchersyncport'];
365
		$syncpass = $config['voucher'][$cpzone]['vouchersyncpass'];
366
		$vouchersyncusername = $config['voucher'][$cpzone]['vouchersyncusername'];
367
		$remote_time_used = xmlrpc_sync_used_voucher($voucher_received, $syncip, $syncport, $syncpass, $vouchersyncusername);
368
	}
369

    
370
	$voucherlck = lock("voucher{$cpzone}", LOCK_EX);
371

    
372
	// read rolls into assoc array with rollid as key and minutes as value
373
	$tickets_per_roll = array();
374
	$minutes_per_roll = array();
375
	if (is_array($config['voucher'][$cpzone]['roll'])) {
376
		foreach ($config['voucher'][$cpzone]['roll'] as $rollent) {
377
			$tickets_per_roll[$rollent['number']] = $rollent['count'];
378
			$minutes_per_roll[$rollent['number']] = $rollent['minutes'];
379
		}
380
	}
381

    
382
	// split into an array. Useful for multiple vouchers given
383
	$a_vouchers_received = preg_split("/[\t\n\r ]+/s", $voucher_received);
384
	$error = 0;
385
	$test_result = array();     // used to display for voucher test option in GUI
386
	$total_minutes = 0;
387
	$first_voucher = "";
388
	$first_voucher_roll = 0;
389

    
390
	// go through all received vouchers, check their valid and extract
391
	// Roll# and Ticket# using the external readvoucher binary
392
	foreach ($a_vouchers_received as $voucher) {
393
		$v = escapeshellarg($voucher);
394
		if (strlen($voucher) < 3) {
395
			$test_result[] = "{$voucher} invalid: Too short!";
396
			captiveportal_syslog("{$voucher} invalid: Too short!");
397
			$error++;
398
			continue;   // seems too short to be a voucher!
399
		}
400

    
401
		$result = exec("/usr/local/bin/voucher -c {$g['varetc_path']}/voucher_{$cpzone}.cfg -k {$g['varetc_path']}/voucher_{$cpzone}.public -- $v");
402
		list($status, $roll, $nr) = explode(" ", $result);
403
		if ($status == "OK") {
404
			if (!$first_voucher) {
405
				// store first voucher. Thats the one we give the timecredit
406
				$first_voucher = $voucher;
407
				$first_voucher_roll = $roll;
408
			}
409
			// check if we have this ticket on a registered roll for this ticket
410
			if ($tickets_per_roll[$roll] && ($nr <= $tickets_per_roll[$roll])) {
411
				// voucher is from a registered roll.
412
				if (!isset($active_vouchers[$roll])) {
413
					$active_vouchers[$roll] = voucher_read_active_db($roll);
414
				}
415
				// valid voucher. Store roll# and ticket#
416
				if (!empty($active_vouchers[$roll][$voucher])) {
417
					list($timestamp, $minutes) = explode(",", $active_vouchers[$roll][$voucher]);
418
					// we have an already active voucher here.
419
					$remaining = intval((($timestamp + (60*$minutes)) - time())/60);
420
					$test_result[] = sprintf(gettext('%1$s (%2$s/%3$s) active and good for %4$d Minutes'), $voucher, $roll, $nr, $remaining);
421
					$total_minutes += $remaining;
422
				} else {
423
					// voucher not used. Check if ticket Id is on the roll (not too high)
424
					// and if the ticket is marked used.
425
					// check if voucher already marked as used
426
					if (!isset($bitstring[$roll])) {
427
						$bitstring[$roll] = voucher_read_used_db($roll);
428
					}
429
					$pos = $nr >> 3; // divide by 8 -> octet
430
					$mask = 1 << ($nr % 8);
431
					if (ord($bitstring[$roll][$pos]) & $mask) {
432
						$test_result[] = "$voucher ($roll/$nr) already used and expired";
433
						captiveportal_syslog("$voucher ($roll/$nr) already used and expired");
434
						$total_minutes = -1;    // voucher expired
435
						$error++;
436
					} else {
437
						// mark bit for this voucher as used
438
						$bitstring[$roll][$pos] = chr(ord($bitstring[$roll][$pos]) | $mask);
439
						$test_result[] = "$voucher ($roll/$nr) good for {$minutes_per_roll[$roll]} Minutes";
440
						$total_minutes += $minutes_per_roll[$roll];
441
					}
442
				}
443
			} else {
444
				$test_result[] = "$voucher ($roll/$nr): not found on any registered Roll";
445
				captiveportal_syslog("$voucher ($roll/$nr): not found on any registered Roll");
446
			}
447
		} else {
448
			// hmm, thats weird ... not what I expected
449
			$test_result[] = "$voucher invalid: $result !!";
450
			captiveportal_syslog("$voucher invalid: $result !!");
451
			$error++;
452
		}
453
	}
454

    
455
	// if this was a test call, we're done. Return the result.
456
	if ($test) {
457
		if ($error) {
458
			$test_result[] = gettext("Access denied!");
459
		} else {
460
			$test_result[] = sprintf(gettext("Access granted for %d Minutes in total."), $total_minutes);
461
		}
462
		unlock($voucherlck);
463

    
464
		return $test_result;
465
	}
466

    
467
	// if we had an error (one of the vouchers is invalid), return 0.
468
	// Discussion: we could return the time remaining for good vouchers, but then
469
	// the user wouldn't know that he used at least one invalid voucher.
470
	if ($error) {
471
		unlock($voucherlck);
472
		if ($total_minutes > 0) {   // probably not needed, but want to make sure
473
			$total_minutes = 0;     // we only report -1 (expired) or 0 (no access)
474
		}
475
		return $total_minutes;       // well, at least one voucher had errors. Say NO ACCESS
476
	}
477

    
478
	// If we did a XMLRPC sync earlier check the timeleft
479
	if (!empty($config['voucher'][$cpzone]['vouchersyncdbip'])) {
480
		if (!is_null($remote_time_used)) {
481
			$total_minutes = $remote_time_used;
482
		} else if ($remote_time_used < $total_minutes) {
483
			$total_minutes -= $remote_time_used;
484
		}
485
	}
486

    
487
	// All given vouchers were valid and this isn't simply a test.
488
	// Write back the used DB's
489
	if (is_array($bitstring)) {
490
		foreach ($bitstring as $roll => $used) {
491
			if (is_array($used)) {
492
				foreach ($used as $u) {
493
					voucher_write_used_db($roll, base64_encode($u));
494
				}
495
			} else {
496
				voucher_write_used_db($roll, base64_encode($used));
497
			}
498
		}
499
	}
500

    
501
	// Active DB: we only add the first voucher if multiple given
502
	// and give that one all the time credit. This allows the user to logout and
503
	// log in later using just the first voucher. It also keeps username limited
504
	// to one voucher and that voucher shows the correct time credit in 'active vouchers'
505
	if (!empty($active_vouchers[$first_voucher_roll][$first_voucher])) {
506
		list($timestamp, $minutes) = explode(",", $active_vouchers[$first_voucher_roll][$first_voucher]);
507
	} else {
508
		$timestamp = time();    // new voucher
509
		$minutes = $total_minutes;
510
	}
511

    
512
	$active_vouchers[$first_voucher_roll][$first_voucher] = "$timestamp,$minutes";
513
	voucher_write_active_db($first_voucher_roll, $active_vouchers[$first_voucher_roll]);
514

    
515
	/* Trigger a sync of the vouchers on config */
516
	send_event("service sync vouchers");
517

    
518
	unlock($voucherlck);
519

    
520
	return $total_minutes;
521
}
522

    
523
function voucher_configure($sync = false) {
524
	global $config, $g, $cpzone;
525

    
526
	if (is_array($config['voucher'])) {
527
		foreach ($config['voucher'] as $voucherzone => $vcfg) {
528
			if (platform_booting()) {
529
				echo gettext("Enabling voucher support... ");
530
			}
531
			$cpzone = $voucherzone;
532
			$error = voucher_configure_zone($sync);
533
			if (platform_booting()) {
534
				if ($error) {
535
					echo "error\n";
536
				} else {
537
					echo "done\n";
538
				}
539
			}
540
		}
541
	}
542
}
543

    
544
function voucher_configure_zone($sync = false) {
545
	global $config, $g, $cpzone;
546

    
547
	if (!isset($config['voucher'][$cpzone]['enable'])) {
548
		return 0;
549
	}
550

    
551
	if ($sync == true) {
552
		captiveportal_syslog("Writing voucher db from sync data...");
553
	}
554

    
555
	$voucherlck = lock("voucher{$cpzone}", LOCK_EX);
556

    
557
	/* write public key used to verify vouchers */
558
	$pubkey = base64_decode($config['voucher'][$cpzone]['publickey']);
559
	$fd = fopen("{$g['varetc_path']}/voucher_{$cpzone}.public", "w");
560
	if (!$fd) {
561
		captiveportal_syslog("Voucher error: cannot write voucher.public\n");
562
		unlock($voucherlck);
563
		return 1;
564
	}
565
	fwrite($fd, $pubkey);
566
	fclose($fd);
567
	@chmod("{$g['varetc_path']}/voucher_{$cpzone}.public", 0600);
568

    
569
	/* write config file used by voucher binary to decode vouchers */
570
	$fd = fopen("{$g['varetc_path']}/voucher_{$cpzone}.cfg", "w");
571
	if (!$fd) {
572
		printf(gettext("Error: cannot write voucher.cfg") . "\n");
573
		unlock($voucherlck);
574
		return 1;
575
	}
576
	fwrite($fd, "{$config['voucher'][$cpzone]['rollbits']},{$config['voucher'][$cpzone]['ticketbits']},{$config['voucher'][$cpzone]['checksumbits']},{$config['voucher'][$cpzone]['magic']},{$config['voucher'][$cpzone]['charset']}\n");
577
	fclose($fd);
578
	@chmod("{$g['varetc_path']}/voucher_{$cpzone}.cfg", 0600);
579
	unlock($voucherlck);
580

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

    
583
		$voucherlck = lock("voucher{$cpzone}", LOCK_EX);
584

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

    
588
			$roll = $rollent['number'];
589
			voucher_write_used_db($roll, $rollent['used']);
590
			$minutes = $rollent['minutes'];
591
			$active_vouchers = array();
592
			$a_active = &$rollent['active'];
593
			if (is_array($a_active)) {
594
				foreach ($a_active as $activent) {
595
					$voucher = $activent['voucher'];
596
					$timestamp = $activent['timestamp'];
597
					$minutes = $activent['minutes'];
598
					// its tempting to check for expired timestamps, but during
599
					// bootup, we most likely don't have the correct time.
600
					$active_vouchers[$voucher] = "$timestamp,$minutes";
601
				}
602
			}
603
			voucher_write_active_db($roll, $active_vouchers);
604
		}
605

    
606
		unlock($voucherlck);
607
	}
608

    
609
	return 0;
610
}
611

    
612
/* write bitstring of used vouchers to ramdisk.
613
 * Bitstring must already be base64_encoded!
614
 */
615
function voucher_write_used_db($roll, $vdb) {
616
	global $g, $cpzone;
617

    
618
	$fd = fopen("{$g['vardb_path']}/voucher_{$cpzone}_used_$roll.db", "w");
619
	if ($fd) {
620
		fwrite($fd, $vdb . "\n");
621
		fclose($fd);
622
	} else {
623
		voucher_log(LOG_ERR, sprintf(gettext('cant write %1$s/voucher_%s_used_%2$s.db'), $g['vardb_path'], $cpzone, $roll));
624
	}
625
}
626

    
627
/* return assoc array of active vouchers with activation timestamp
628
 * voucher is index.
629
 */
630
function voucher_read_active_db($roll) {
631
	global $g, $cpzone;
632

    
633
	$active = array();
634
	$dirty = 0;
635
	$file = "{$g['vardb_path']}/voucher_{$cpzone}_active_$roll.db";
636
	if (file_exists($file)) {
637
		$fd = fopen($file, "r");
638
		if ($fd) {
639
			while (!feof($fd)) {
640
				$line = trim(fgets($fd));
641
				if ($line) {
642
					list($voucher, $timestamp, $minutes) = explode(",", $line); // voucher,timestamp
643
					if ((($timestamp + (60*$minutes)) - time()) > 0) {
644
						$active[$voucher] = "$timestamp,$minutes";
645
					} else {
646
						$dirty=1;
647
					}
648
				}
649
			}
650
			fclose($fd);
651
			if ($dirty) { // if we found expired entries, lets save our snapshot
652
				voucher_write_active_db($roll, $active);
653

    
654
				/* Trigger a sync of the vouchers on config */
655
				send_event("service sync vouchers");
656
			}
657
		}
658
	}
659
	return $active;
660
}
661

    
662
/* store array of active vouchers back to DB */
663
function voucher_write_active_db($roll, $active) {
664
	global $g, $cpzone;
665

    
666
	if (!is_array($active)) {
667
		return;
668
	}
669
	$fd = fopen("{$g['vardb_path']}/voucher_{$cpzone}_active_$roll.db", "w");
670
	if ($fd) {
671
		foreach ($active as $voucher => $value) {
672
			fwrite($fd, "$voucher,$value\n");
673
		}
674
		fclose($fd);
675
	}
676
}
677

    
678
/* return how many vouchers are marked used on a roll */
679
function voucher_used_count($roll) {
680
	global $g, $cpzone;
681

    
682
	$bitstring = voucher_read_used_db($roll);
683
	$max = strlen($bitstring) * 8;
684
	$used = 0;
685
	for ($i = 1; $i <= $max; $i++) {
686
		// check if ticket already used or not.
687
		$pos = $i >> 3;            // divide by 8 -> octet
688
		$mask = 1 << ($i % 8);  // mask to test bit in octet
689
		if (ord($bitstring[$pos]) & $mask) {
690
			$used++;
691
		}
692
	}
693
	unset($bitstring);
694

    
695
	return $used;
696
}
697

    
698
function voucher_read_used_db($roll) {
699
	global $g, $cpzone;
700

    
701
	$vdb = "";
702
	$file = "{$g['vardb_path']}/voucher_{$cpzone}_used_$roll.db";
703
	if (file_exists($file)) {
704
		$fd = fopen($file, "r");
705
		if ($fd) {
706
			$vdb = trim(fgets($fd));
707
			fclose($fd);
708
		} else {
709
			voucher_log(LOG_ERR, sprintf(gettext('cant read %1$s/voucher_%s_used_%2$s.db'), $g['vardb_path'], $cpzone, $roll));
710
		}
711
	}
712
	return base64_decode($vdb);
713
}
714

    
715
function voucher_unlink_db($roll) {
716
	global $g, $cpzone;
717
	@unlink("{$g['vardb_path']}/voucher_{$cpzone}_used_$roll.db");
718
	@unlink("{$g['vardb_path']}/voucher_{$cpzone}_active_$roll.db");
719
}
720

    
721
/* we share the log with captiveportal for now */
722
function voucher_log($priority, $message) {
723

    
724
	$message = trim($message);
725
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
726
	syslog($priority, sprintf(gettext("Voucher: %s"), $message));
727
	closelog();
728
}
729

    
730
/* Save active and used voucher DB into XML config and write it to flash
731
 * Called during reboot -> system_reboot_cleanup() and every active voucher change
732
 */
733
function voucher_save_db_to_config() {
734
	global $config, $g, $cpzone;
735

    
736
	if (is_array($config['voucher'])) {
737
		foreach ($config['voucher'] as $voucherzone => $vcfg) {
738
			$cpzone = $voucherzone;
739
			voucher_save_db_to_config_zone();
740
		}
741
	}
742
}
743

    
744
function voucher_save_db_to_config_zone() {
745
	global $config, $g, $cpzone;
746

    
747
	if (!isset($config['voucher'][$cpzone]['enable'])) {
748
		return;   // no vouchers or don't want to save DB's
749
	}
750

    
751
	if (!is_array($config['voucher'][$cpzone]['roll'])) {
752
		return;
753
	}
754

    
755
	$voucherlck = lock("voucher{$cpzone}", LOCK_EX);
756

    
757
	// walk all active rolls and save runtime DB's to flash
758
	$a_roll = &$config['voucher'][$cpzone]['roll'];
759
	while (list($key, $value) = each($a_roll)) {
760
		$rollent = &$a_roll[$key];
761
		$roll = $rollent['number'];
762
		$bitmask = voucher_read_used_db($roll);
763
		$rollent['used'] = base64_encode($bitmask);
764
		$active_vouchers = voucher_read_active_db($roll);
765
		$db = array();
766
		$dbi = 1;
767
		foreach ($active_vouchers as $voucher => $line) {
768
			list($timestamp, $minutes) = explode(",", $line);
769
			$activent['voucher'] = $voucher;
770
			$activent['timestamp'] = $timestamp;
771
			$activent['minutes'] = $minutes;
772
			$db["v{$dbi}"] = $activent;
773
			$dbi++;
774
		}
775
		$rollent['active'] = $db;
776
		unset($active_vouchers);
777
	}
778

    
779
	unlock($voucherlck);
780

    
781
	write_config("Syncing vouchers");
782
	return;
783
}
784

    
785
?>
(58-58/68)