Project

General

Profile

Download (27.7 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php
2
/*
3
	voucher.inc
4

    
5
	part of pfSense (https://www.pfsense.org)
6
	Copyright (C) 2007 Marcel Wiget <mwiget@mac.com>
7
	Copyright (c) 2007-2016 Electric Sheep Fencing, LLC.
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
	Redistribution and use in source and binary forms, with or without
15
	modification, are permitted provided that the following conditions are met:
16

    
17
	1. Redistributions of source code must retain the above copyright notice,
18
	   this list of conditions and the following disclaimer.
19

    
20
	2. Redistributions in binary form must reproduce the above copyright
21
	   notice, this list of conditions and the following disclaimer in
22
	   the documentation and/or other materials provided with the
23
	   distribution.
24

    
25
	3. All advertising materials mentioning features or use of this software
26
	   must display the following acknowledgment:
27
	   "This product includes software developed by the pfSense Project
28
	   for use in the pfSense® software distribution. (http://www.pfsense.org/).
29

    
30
	4. The names "pfSense" and "pfSense Project" must not be used to
31
	   endorse or promote products derived from this software without
32
	   prior written permission. For written permission, please contact
33
	   coreteam@pfsense.org.
34

    
35
	5. Products derived from this software may not be called "pfSense"
36
	   nor may "pfSense" appear in their names without prior written
37
	   permission of the Electric Sheep Fencing, LLC.
38

    
39
	6. Redistributions of any form whatsoever must retain the following
40
	   acknowledgment:
41

    
42
	"This product includes software developed by the pfSense Project
43
	for use in the pfSense software distribution (http://www.pfsense.org/).
44

    
45
	THIS SOFTWARE IS PROVIDED BY THE pfSense PROJECT ``AS IS'' AND ANY
46
	EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
47
	IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
48
	PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE pfSense PROJECT OR
49
	ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
50
	SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
51
	NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
52
	LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
53
	HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
54
	STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
55
	ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
56
	OF THE POSSIBILITY OF SUCH DAMAGE.
57
*/
58

    
59
/* include all configuration functions */
60
if (!function_exists('captiveportal_syslog')) {
61
	require_once("captiveportal.inc");
62
}
63

    
64
function xmlrpc_sync_voucher_expire($vouchers, $syncip, $port, $password, $username) {
65
	global $g, $config, $cpzone;
66
	require_once("xmlrpc.inc");
67

    
68
	$protocol = "http";
69
	if (is_array($config['system']) && is_array($config['system']['webgui']) && !empty($config['system']['webgui']['protocol']) &&
70
	    $config['system']['webgui']['protocol'] == "https") {
71
		$protocol = "https";
72
	}
73
	if ($protocol == "https" || $port == "443") {
74
		$url = "https://{$syncip}";
75
	} else {
76
		$url = "http://{$syncip}";
77
	}
78

    
79
	/* Construct code that is run on remote machine */
80
	$method = 'pfsense.exec_php';
81
	$execcmd = <<<EOF
82
	global \$cpzone;
83
	require_once('/etc/inc/captiveportal.inc');
84
	require_once('/etc/inc/voucher.inc');
85
	\$cpzone = "$cpzone";
86
	voucher_expire("$vouchers");
87

    
88
EOF;
89

    
90
	/* assemble xmlrpc payload */
91
	$params = array(
92
		XML_RPC_encode($password),
93
		XML_RPC_encode($execcmd)
94
	);
95

    
96
	log_error(sprintf(gettext("Captive Portal Voucher XMLRPC sync data %s."), $url . ":" . $port));
97
	$msg = new XML_RPC_Message($method, $params);
98
	$cli = new XML_RPC_Client('/xmlrpc.php', $url, $port);
99
	$cli->setCredentials($username, $password);
100
	$resp = $cli->send($msg, "250");
101
	if (!is_object($resp)) {
102
		$error = sprintf(gettext("A communications error occurred while attempting CaptivePortalVoucherSync XMLRPC sync with %s (pfsense.exec_php)."), $url . ":" . $port);
103
		log_error($error);
104
		file_notice("CaptivePortalVoucherSync", $error, "Communications error occurred", "");
105
		return false;
106
	} elseif ($resp->faultCode()) {
107
		$error = sprintf(gettext('An error code was received while attempting CaptivePortalVoucherSync XMLRPC sync with %1$s - Code %2$s'), $url . ":" . $port, $resp->faultCode() . ": " . $resp->faultString());
108
		log_error($error);
109
		file_notice("CaptivePortalVoucherSync", $error, "Error code received", "");
110
		return false;
111
	} else {
112
		log_error(sprintf(gettext("CaptivePortalVoucherSync XMLRPC reload data success with %s (pfsense.exec_php)."), $url . ":" . $port));
113
	}
114

    
115
	$toreturn = XML_RPC_Decode($resp->value());
116

    
117
	return $toreturn;
118
}
119

    
120
function xmlrpc_sync_voucher_disconnect($dbent, $syncip, $port, $password, $username, $term_cause = 1, $stop_time = null) {
121
	global $g, $config, $cpzone;
122
	require_once("xmlrpc.inc");
123

    
124
	$protocol = "http";
125
	if (is_array($config['system']) && is_array($config['system']['webgui']) && !empty($config['system']['webgui']['protocol']) &&
126
	    $config['system']['webgui']['protocol'] == "https") {
127
		$protocol = "https";
128
	}
129
	if ($protocol == "https" || $port == "443") {
130
		$url = "https://{$syncip}";
131
	} else {
132
		$url = "http://{$syncip}";
133
	}
134

    
135
	/* Construct code that is run on remote machine */
136
	$dbent_str = serialize($dbent);
137
	$tmp_stop_time = (isset($stop_time)) ? $stop_time : "null";
138
	$method = 'pfsense.exec_php';
139
	$execcmd = <<<EOF
140
	global \$cpzone;
141
	require_once('/etc/inc/captiveportal.inc');
142
	require_once('/etc/inc/voucher.inc');
143
	\$cpzone = "$cpzone";
144
	\$radiusservers = captiveportal_get_radius_servers();
145
	\$dbent = unserialize("$dbent_str");
146
	captiveportal_disconnect(\$dbent, \$radiusservers, $term_cause, $tmp_stop_time);
147

    
148
EOF;
149

    
150
	/* assemble xmlrpc payload */
151
	$params = array(
152
		XML_RPC_encode($password),
153
		XML_RPC_encode($execcmd)
154
	);
155

    
156
	log_error(sprintf(gettext("Captive Portal Voucher XMLRPC sync data %s."), $url . ":" . $port));
157
	$msg = new XML_RPC_Message($method, $params);
158
	$cli = new XML_RPC_Client('/xmlrpc.php', $url, $port);
159
	$cli->setCredentials($username, $password);
160
	$resp = $cli->send($msg, "250");
161
	if (!is_object($resp)) {
162
		$error = sprintf(gettext("A communications error occurred while attempting CaptivePortalVoucherSync XMLRPC sync with %s (pfsense.exec_php)."), $url . ":" . $port);
163
		log_error($error);
164
		file_notice("CaptivePortalVoucherSync", $error, "Communications error occurred", "");
165
		return false;
166
	} elseif ($resp->faultCode()) {
167
		$error = sprintf(gettext('An error code was received while attempting CaptivePortalVoucherSync XMLRPC sync with %1$s - Code %2$s'), $url . ":" . $port, $resp->faultCode() . ": " . $resp->faultString());
168
		log_error($error);
169
		file_notice("CaptivePortalVoucherSync", $error, "Error code received", "");
170
		return false;
171
	} else {
172
		log_error(sprintf(gettext("CaptivePortalVoucherSync XMLRPC reload data success with %s (pfsense.exec_php)."), $url . ":" . $port));
173
	}
174

    
175
	$toreturn = XML_RPC_Decode($resp->value());
176

    
177
	return $toreturn;
178
}
179

    
180
function xmlrpc_sync_used_voucher($voucher_received, $syncip, $port, $password, $username) {
181
	global $g, $config, $cpzone;
182
	require_once("xmlrpc.inc");
183

    
184
	$protocol = "http";
185
	if (is_array($config['system']) && is_array($config['system']['webgui']) && !empty($config['system']['webgui']['protocol']) &&
186
	    $config['system']['webgui']['protocol'] == "https") {
187
		$protocol = "https";
188
	}
189
	if ($protocol == "https" || $port == "443") {
190
		$url = "https://{$syncip}";
191
	} else {
192
		$url = "http://{$syncip}";
193
	}
194

    
195
	/* Construct code that is run on remote machine */
196
	$method = 'pfsense.exec_php';
197
	$execcmd = <<<EOF
198
	global \$cpzone;
199
	require_once('/etc/inc/voucher.inc');
200
	\$cpzone = "$cpzone";
201
	\$timeleft = voucher_auth("$voucher_received");
202
	\$toreturn = array();
203
	\$toreturn['timeleft'] = \$timeleft;
204
	\$toreturn['voucher'] = array();
205
	\$toreturn['voucher']['roll'] = \$config['voucher'][\$cpzone]['roll'];
206

    
207
EOF;
208

    
209
	/* assemble xmlrpc payload */
210
	$params = array(
211
		XML_RPC_encode($password),
212
		XML_RPC_encode($execcmd)
213
	);
214

    
215
	log_error(sprintf(gettext("Captive Portal Voucher XMLRPC sync data %s."), $url . ":" . $port));
216
	$msg = new XML_RPC_Message($method, $params);
217
	$cli = new XML_RPC_Client('/xmlrpc.php', $url, $port);
218
	$cli->setCredentials($username, $password);
219
	$resp = $cli->send($msg, "250");
220
	if (!is_object($resp)) {
221
		$error = sprintf(gettext("A communications error occurred while attempting CaptivePortalVoucherSync XMLRPC sync with %s (pfsense.exec_php)."), $url . ":" . $port);
222
		log_error($error);
223
		file_notice("CaptivePortalVoucherSync", $error, "Communications error occurred", "");
224
		return null; // $timeleft
225
	} elseif ($resp->faultCode()) {
226
		$error = sprintf(gettext('An error code was received while attempting CaptivePortalVoucherSync XMLRPC sync with %1$s - Code %2$s'), $url . ":" . $port, $resp->faultCode() . ": " . $resp->faultString());
227
		log_error($error);
228
		file_notice("CaptivePortalVoucherSync", $error, "Error code received", "");
229
		return null; // $timeleft
230
	} else {
231
		log_error(sprintf(gettext("CaptivePortalVoucherSync XMLRPC reload data success with %s (pfsense.exec_php)."), $url . ":" . $port));
232
	}
233
	$toreturn = XML_RPC_Decode($resp->value());
234
	if (!is_array($config['voucher'])) {
235
		$config['voucher'] = array();
236
	}
237

    
238
	if (is_array($toreturn['voucher']) && is_array($toreturn['voucher']['roll'])) {
239
		$config['voucher'][$cpzone]['roll'] = $toreturn['voucher']['roll'];
240
		write_config(sprintf(gettext("Captive Portal Voucher database synchronized with %s"), $url));
241
		voucher_configure_zone(true);
242
		unset($toreturn['voucher']);
243
	} else if (!isset($toreturn['timeleft'])) {
244
		return null;
245
	}
246

    
247
	return $toreturn['timeleft'];
248
}
249

    
250
function voucher_expire($voucher_received) {
251
	global $g, $config, $cpzone, $cpzoneid;
252

    
253
	// XMLRPC Call over to the master Voucher node
254
	if (!empty($config['voucher'][$cpzone]['vouchersyncdbip'])) {
255
		$syncip   = $config['voucher'][$cpzone]['vouchersyncdbip'];
256
		$syncport = $config['voucher'][$cpzone]['vouchersyncport'];
257
		$syncpass = $config['voucher'][$cpzone]['vouchersyncpass'];
258
		$vouchersyncusername = $config['voucher'][$cpzone]['vouchersyncusername'];
259
		xmlrpc_sync_voucher_expire($voucher_received, $syncip, $syncport, $syncpass, $vouchersyncusername);
260
	}
261

    
262
	$voucherlck = lock("voucher{$cpzone}", LOCK_EX);
263

    
264
	// read rolls into assoc array with rollid as key and minutes as value
265
	$tickets_per_roll = array();
266
	$minutes_per_roll = array();
267
	if (is_array($config['voucher'][$cpzone]['roll'])) {
268
		foreach ($config['voucher'][$cpzone]['roll'] as $rollent) {
269
			$tickets_per_roll[$rollent['number']] = $rollent['count'];
270
			$minutes_per_roll[$rollent['number']] = $rollent['minutes'];
271
		}
272
	}
273

    
274
	// split into an array. Useful for multiple vouchers given
275
	$a_vouchers_received = preg_split("/[\t\n\r ]+/s", $voucher_received);
276
	$active_dirty = false;
277
	$unsetindexes = array();
278

    
279
	// go through all received vouchers, check their valid and extract
280
	// Roll# and Ticket# using the external readvoucher binary
281
	foreach ($a_vouchers_received as $voucher) {
282
		$v = escapeshellarg($voucher);
283
		if (strlen($voucher) < 5) {
284
			captiveportal_syslog("${voucher} invalid: Too short!");
285
			continue;   // seems too short to be a voucher!
286
		}
287

    
288
		unset($output);
289
		$_gb = exec("/usr/local/bin/voucher -c {$g['varetc_path']}/voucher_{$cpzone}.cfg -k {$g['varetc_path']}/voucher_{$cpzone}.public -- $v", $output);
290
		list($status, $roll, $nr) = explode(" ", $output[0]);
291
		if ($status == "OK") {
292
			// check if we have this ticket on a registered roll for this ticket
293
			if ($tickets_per_roll[$roll] && ($nr <= $tickets_per_roll[$roll])) {
294
				// voucher is from a registered roll.
295
				if (!isset($active_vouchers[$roll])) {
296
					$active_vouchers[$roll] = voucher_read_active_db($roll);
297
				}
298
				// valid voucher. Store roll# and ticket#
299
				if (!empty($active_vouchers[$roll][$voucher])) {
300
					$active_dirty = true;
301
					unset($active_vouchers[$roll][$voucher]);
302
				}
303
				// check if voucher already marked as used
304
				if (!isset($bitstring[$roll])) {
305
					$bitstring[$roll] = voucher_read_used_db($roll);
306
				}
307
				$pos = $nr >> 3; // divide by 8 -> octet
308
				$mask = 1 << ($nr % 8);
309
				// mark bit for this voucher as used
310
				if (!(ord($bitstring[$roll][$pos]) & $mask)) {
311
					$bitstring[$roll][$pos] = chr(ord($bitstring[$roll][$pos]) | $mask);
312
				}
313
				captiveportal_syslog("{$voucher} ({$roll}/{$nr}) forced to expire");
314

    
315
				/* Check if this voucher has any active sessions */
316
				$cpentry = captiveportal_read_db("WHERE username = '{$voucher}'");
317
				if (!empty($cpentry) && !empty($cpentry[0])) {
318
					if (empty($cpzoneid) && !empty($config['captiveportal'][$cpzone])) {
319
						$cpzoneid = $config['captiveportal'][$cpzone]['zoneid'];
320
					}
321
					$cpentry = $cpentry[0];
322
					captiveportal_disconnect($cpentry, null, 13);
323
					captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "FORCLY TERMINATING VOUCHER {$voucher} SESSION");
324
					$unsetindexes[] = $cpentry[5];
325
				}
326
			} else {
327
				captiveportal_syslog(sprintf(gettext('%1$s (%2$s/%3$s): not found on any registered Roll'), $voucher, $roll, $nr));
328
			}
329
		} else {
330
			// hmm, thats weird ... not what I expected
331
			captiveportal_syslog(sprintf(gettext('%1$s invalid: %2$s!!'), $voucher, $output[0]));
332
		}
333
	}
334

    
335
	// Refresh active DBs
336
	if ($active_dirty == true) {
337
		foreach ($active_vouchers as $roll => $active) {
338
			voucher_write_active_db($roll, $active);
339
		}
340
		unset($active_vouchers);
341

    
342
		/* Trigger a sync of the vouchers on config */
343
		send_event("service sync vouchers");
344
	}
345

    
346
	// Write back the used DB's
347
	if (is_array($bitstring)) {
348
		foreach ($bitstring as $roll => $used) {
349
			if (is_array($used)) {
350
				foreach ($used as $u) {
351
					voucher_write_used_db($roll, base64_encode($u));
352
				}
353
			} else {
354
				voucher_write_used_db($roll, base64_encode($used));
355
			}
356
		}
357
		unset($bitstring);
358
	}
359

    
360
	unlock($voucherlck);
361

    
362
	/* Write database */
363
	if (!empty($unsetindexes)) {
364
		captiveportal_remove_entries($unsetindexes);
365
	}
366

    
367
	return true;
368
}
369

    
370
/*
371
 * Authenticate a voucher and return the remaining time credit in minutes
372
 * if $test is set, don't mark the voucher as used nor add it to the list
373
 * of active vouchers
374
 * If $test is set, simply test the voucher. Don't change anything
375
 * but return a more verbose error and result message back
376
 */
377
function voucher_auth($voucher_received, $test = 0) {
378
	global $g, $config, $cpzone, $dbc;
379

    
380
	if (!isset($config['voucher'][$cpzone]['enable'])) {
381
		return 0;
382
	}
383

    
384
	// XMLRPC Call over to the master Voucher node
385
	if (!empty($config['voucher'][$cpzone]['vouchersyncdbip'])) {
386
		$syncip   = $config['voucher'][$cpzone]['vouchersyncdbip'];
387
		$syncport = $config['voucher'][$cpzone]['vouchersyncport'];
388
		$syncpass = $config['voucher'][$cpzone]['vouchersyncpass'];
389
		$vouchersyncusername = $config['voucher'][$cpzone]['vouchersyncusername'];
390
		$remote_time_used = xmlrpc_sync_used_voucher($voucher_received, $syncip, $syncport, $syncpass, $vouchersyncusername);
391
	}
392

    
393
	$voucherlck = lock("voucher{$cpzone}", LOCK_EX);
394

    
395
	// read rolls into assoc array with rollid as key and minutes as value
396
	$tickets_per_roll = array();
397
	$minutes_per_roll = array();
398
	if (is_array($config['voucher'][$cpzone]['roll'])) {
399
		foreach ($config['voucher'][$cpzone]['roll'] as $rollent) {
400
			$tickets_per_roll[$rollent['number']] = $rollent['count'];
401
			$minutes_per_roll[$rollent['number']] = $rollent['minutes'];
402
		}
403
	}
404

    
405
	// split into an array. Useful for multiple vouchers given
406
	$a_vouchers_received = preg_split("/[\t\n\r ]+/s", $voucher_received);
407
	$error = 0;
408
	$test_result = array();     // used to display for voucher test option in GUI
409
	$total_minutes = 0;
410
	$first_voucher = "";
411
	$first_voucher_roll = 0;
412

    
413
	// go through all received vouchers, check their valid and extract
414
	// Roll# and Ticket# using the external readvoucher binary
415
	foreach ($a_vouchers_received as $voucher) {
416
		$v = escapeshellarg($voucher);
417
		if (strlen($voucher) < 5) {
418
			$voucher_err_text = sprintf(gettext("%s invalid: Too short!"), $voucher);
419
			$test_result[] = $voucher_err_text;
420
			captiveportal_syslog($voucher_err_text);
421
			$error++;
422
			continue;   // seems too short to be a voucher!
423
		}
424

    
425
		$result = exec("/usr/local/bin/voucher -c {$g['varetc_path']}/voucher_{$cpzone}.cfg -k {$g['varetc_path']}/voucher_{$cpzone}.public -- $v");
426
		list($status, $roll, $nr) = explode(" ", $result);
427
		if ($status == "OK") {
428
			if (!$first_voucher) {
429
				// store first voucher. Thats the one we give the timecredit
430
				$first_voucher = $voucher;
431
				$first_voucher_roll = $roll;
432
			}
433
			// check if we have this ticket on a registered roll for this ticket
434
			if ($tickets_per_roll[$roll] && ($nr <= $tickets_per_roll[$roll])) {
435
				// voucher is from a registered roll.
436
				if (!isset($active_vouchers[$roll])) {
437
					$active_vouchers[$roll] = voucher_read_active_db($roll);
438
				}
439
				// valid voucher. Store roll# and ticket#
440
				if (!empty($active_vouchers[$roll][$voucher])) {
441
					list($timestamp, $minutes) = explode(",", $active_vouchers[$roll][$voucher]);
442
					// we have an already active voucher here.
443
					$remaining = intval((($timestamp + (60*$minutes)) - time())/60);
444
					$test_result[] = sprintf(gettext('%1$s (%2$s/%3$s) active and good for %4$d Minutes'), $voucher, $roll, $nr, $remaining);
445
					$total_minutes += $remaining;
446
				} else {
447
					// voucher not used. Check if ticket Id is on the roll (not too high)
448
					// and if the ticket is marked used.
449
					// check if voucher already marked as used
450
					if (!isset($bitstring[$roll])) {
451
						$bitstring[$roll] = voucher_read_used_db($roll);
452
					}
453
					$pos = $nr >> 3; // divide by 8 -> octet
454
					$mask = 1 << ($nr % 8);
455
					if (ord($bitstring[$roll][$pos]) & $mask) {
456
						$voucher_err_text = sprintf(gettext('%1$s (%2$s/%3$s) already used and expired'), $voucher, $roll, $nr);
457
						$test_result[] = $voucher_err_text;
458
						captiveportal_syslog($voucher_err_text);
459
						$total_minutes = -1;    // voucher expired
460
						$error++;
461
					} else {
462
						// mark bit for this voucher as used
463
						$bitstring[$roll][$pos] = chr(ord($bitstring[$roll][$pos]) | $mask);
464
						$test_result[] = sprintf(gettext('%1$s (%2$s/%3$s) good for %4$s Minutes'), $voucher, $roll, $nr, $minutes_per_roll[$roll]);
465
						$total_minutes += $minutes_per_roll[$roll];
466
					}
467
				}
468
			} else {
469
				$voucher_err_text = sprintf(gettext('%1$s (%2$s/%3$s): not found on any registered Roll'), $voucher, $roll, $nr);
470
				$test_result[] = $voucher_err_text;
471
				captiveportal_syslog($voucher_err_text);
472
			}
473
		} else {
474
			// hmm, thats weird ... not what I expected
475
			$voucher_err_text = sprintf(gettext('%1$s invalid: %2$s !!'), $voucher, $result);
476
			$test_result[] = $voucher_err_text;
477
			captiveportal_syslog($voucher_err_text);
478
			$error++;
479
		}
480
	}
481

    
482
	// if this was a test call, we're done. Return the result.
483
	if ($test) {
484
		if ($error) {
485
			$test_result[] = gettext("Access denied!");
486
		} else {
487
			$test_result[] = sprintf(gettext("Access granted for %d Minutes in total."), $total_minutes);
488
		}
489
		unlock($voucherlck);
490

    
491
		return $test_result;
492
	}
493

    
494
	// if we had an error (one of the vouchers is invalid), return 0.
495
	// Discussion: we could return the time remaining for good vouchers, but then
496
	// the user wouldn't know that he used at least one invalid voucher.
497
	if ($error) {
498
		unlock($voucherlck);
499
		if ($total_minutes > 0) {   // probably not needed, but want to make sure
500
			$total_minutes = 0;     // we only report -1 (expired) or 0 (no access)
501
		}
502
		return $total_minutes;       // well, at least one voucher had errors. Say NO ACCESS
503
	}
504

    
505
	// If we did a XMLRPC sync earlier check the timeleft
506
	if (!empty($config['voucher'][$cpzone]['vouchersyncdbip'])) {
507
		if (!is_null($remote_time_used)) {
508
			$total_minutes = $remote_time_used;
509
		} else if ($remote_time_used < $total_minutes) {
510
			$total_minutes -= $remote_time_used;
511
		}
512
	}
513

    
514
	// All given vouchers were valid and this isn't simply a test.
515
	// Write back the used DB's
516
	if (is_array($bitstring)) {
517
		foreach ($bitstring as $roll => $used) {
518
			if (is_array($used)) {
519
				foreach ($used as $u) {
520
					voucher_write_used_db($roll, base64_encode($u));
521
				}
522
			} else {
523
				voucher_write_used_db($roll, base64_encode($used));
524
			}
525
		}
526
	}
527

    
528
	// Active DB: we only add the first voucher if multiple given
529
	// and give that one all the time credit. This allows the user to logout and
530
	// log in later using just the first voucher. It also keeps username limited
531
	// to one voucher and that voucher shows the correct time credit in 'active vouchers'
532
	if (!empty($active_vouchers[$first_voucher_roll][$first_voucher])) {
533
		list($timestamp, $minutes) = explode(",", $active_vouchers[$first_voucher_roll][$first_voucher]);
534
	} else {
535
		$timestamp = time();    // new voucher
536
		$minutes = $total_minutes;
537
	}
538

    
539
	$active_vouchers[$first_voucher_roll][$first_voucher] = "$timestamp,$minutes";
540
	voucher_write_active_db($first_voucher_roll, $active_vouchers[$first_voucher_roll]);
541

    
542
	/* Trigger a sync of the vouchers on config */
543
	send_event("service sync vouchers");
544

    
545
	unlock($voucherlck);
546

    
547
	return $total_minutes;
548
}
549

    
550
function voucher_configure($sync = false) {
551
	global $config, $g, $cpzone;
552

    
553
	if (is_array($config['voucher'])) {
554
		foreach ($config['voucher'] as $voucherzone => $vcfg) {
555
			if (platform_booting()) {
556
				echo gettext("Enabling voucher support... ");
557
			}
558
			$cpzone = $voucherzone;
559
			$error = voucher_configure_zone($sync);
560
			if (platform_booting()) {
561
				if ($error) {
562
					echo "error\n";
563
				} else {
564
					echo "done\n";
565
				}
566
			}
567
		}
568
	}
569
}
570

    
571
function voucher_configure_zone($sync = false) {
572
	global $config, $g, $cpzone;
573

    
574
	if (!isset($config['voucher'][$cpzone]['enable'])) {
575
		return 0;
576
	}
577

    
578
	if ($sync == true) {
579
		captiveportal_syslog("Writing voucher db from sync data...");
580
	}
581

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

    
584
	/* write public key used to verify vouchers */
585
	$pubkey = base64_decode($config['voucher'][$cpzone]['publickey']);
586
	$fd = fopen("{$g['varetc_path']}/voucher_{$cpzone}.public", "w");
587
	if (!$fd) {
588
		captiveportal_syslog("Voucher error: cannot write voucher.public\n");
589
		unlock($voucherlck);
590
		return 1;
591
	}
592
	fwrite($fd, $pubkey);
593
	fclose($fd);
594
	@chmod("{$g['varetc_path']}/voucher_{$cpzone}.public", 0600);
595

    
596
	/* write config file used by voucher binary to decode vouchers */
597
	$fd = fopen("{$g['varetc_path']}/voucher_{$cpzone}.cfg", "w");
598
	if (!$fd) {
599
		printf(gettext("Error: cannot write voucher.cfg") . "\n");
600
		unlock($voucherlck);
601
		return 1;
602
	}
603
	fwrite($fd, "{$config['voucher'][$cpzone]['rollbits']},{$config['voucher'][$cpzone]['ticketbits']},{$config['voucher'][$cpzone]['checksumbits']},{$config['voucher'][$cpzone]['magic']},{$config['voucher'][$cpzone]['charset']}\n");
604
	fclose($fd);
605
	@chmod("{$g['varetc_path']}/voucher_{$cpzone}.cfg", 0600);
606
	unlock($voucherlck);
607

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

    
610
		$voucherlck = lock("voucher{$cpzone}", LOCK_EX);
611

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

    
615
			$roll = $rollent['number'];
616
			$len = ($rollent['count'] >> 3) + 1;
617
			if (strlen(base64_decode($rollent['used'])) != $len) {
618
				$rollent['used'] = base64_encode(str_repeat("\000", $len));
619
			}
620
			voucher_write_used_db($roll, $rollent['used']);
621
			$minutes = $rollent['minutes'];
622
			$active_vouchers = array();
623
			$a_active = &$rollent['active'];
624
			if (is_array($a_active)) {
625
				foreach ($a_active as $activent) {
626
					$voucher = $activent['voucher'];
627
					$timestamp = $activent['timestamp'];
628
					$minutes = $activent['minutes'];
629
					// its tempting to check for expired timestamps, but during
630
					// bootup, we most likely don't have the correct time.
631
					$active_vouchers[$voucher] = "$timestamp,$minutes";
632
				}
633
			}
634
			voucher_write_active_db($roll, $active_vouchers);
635
		}
636

    
637
		unlock($voucherlck);
638
	}
639

    
640
	return 0;
641
}
642

    
643
/* write bitstring of used vouchers to ramdisk.
644
 * Bitstring must already be base64_encoded!
645
 */
646
function voucher_write_used_db($roll, $vdb) {
647
	global $g, $cpzone;
648

    
649
	$fd = fopen("{$g['vardb_path']}/voucher_{$cpzone}_used_$roll.db", "w");
650
	if ($fd) {
651
		fwrite($fd, $vdb . "\n");
652
		fclose($fd);
653
	} else {
654
		voucher_log(LOG_ERR, sprintf(gettext('cant write %1$s/voucher_%2$s_used_%3$s.db'), $g['vardb_path'], $cpzone, $roll));
655
	}
656
}
657

    
658
/* return assoc array of active vouchers with activation timestamp
659
 * voucher is index.
660
 */
661
function voucher_read_active_db($roll) {
662
	global $g, $cpzone;
663

    
664
	$active = array();
665
	$dirty = 0;
666
	$file = "{$g['vardb_path']}/voucher_{$cpzone}_active_$roll.db";
667
	if (file_exists($file)) {
668
		$fd = fopen($file, "r");
669
		if ($fd) {
670
			while (!feof($fd)) {
671
				$line = trim(fgets($fd));
672
				if ($line) {
673
					list($voucher, $timestamp, $minutes) = explode(",", $line); // voucher,timestamp
674
					if ((($timestamp + (60*$minutes)) - time()) > 0) {
675
						$active[$voucher] = "$timestamp,$minutes";
676
					} else {
677
						$dirty=1;
678
					}
679
				}
680
			}
681
			fclose($fd);
682
			if ($dirty) { // if we found expired entries, lets save our snapshot
683
				voucher_write_active_db($roll, $active);
684

    
685
				/* Trigger a sync of the vouchers on config */
686
				send_event("service sync vouchers");
687
			}
688
		}
689
	}
690
	return $active;
691
}
692

    
693
/* store array of active vouchers back to DB */
694
function voucher_write_active_db($roll, $active) {
695
	global $g, $cpzone;
696

    
697
	if (!is_array($active)) {
698
		return;
699
	}
700
	$fd = fopen("{$g['vardb_path']}/voucher_{$cpzone}_active_$roll.db", "w");
701
	if ($fd) {
702
		foreach ($active as $voucher => $value) {
703
			fwrite($fd, "$voucher,$value\n");
704
		}
705
		fclose($fd);
706
	}
707
}
708

    
709
/* return how many vouchers are marked used on a roll */
710
function voucher_used_count($roll) {
711
	global $g, $cpzone;
712

    
713
	$bitstring = voucher_read_used_db($roll);
714
	$max = strlen($bitstring) * 8;
715
	$used = 0;
716
	for ($i = 1; $i <= $max; $i++) {
717
		// check if ticket already used or not.
718
		$pos = $i >> 3;            // divide by 8 -> octet
719
		$mask = 1 << ($i % 8);  // mask to test bit in octet
720
		if (ord($bitstring[$pos]) & $mask) {
721
			$used++;
722
		}
723
	}
724
	unset($bitstring);
725

    
726
	return $used;
727
}
728

    
729
function voucher_read_used_db($roll) {
730
	global $g, $cpzone;
731

    
732
	$vdb = "";
733
	$file = "{$g['vardb_path']}/voucher_{$cpzone}_used_$roll.db";
734
	if (file_exists($file)) {
735
		$fd = fopen($file, "r");
736
		if ($fd) {
737
			$vdb = trim(fgets($fd));
738
			fclose($fd);
739
		} else {
740
			voucher_log(LOG_ERR, sprintf(gettext('cant read %1$s/voucher_%2$s_used_%3$s.db'), $g['vardb_path'], $cpzone, $roll));
741
		}
742
	}
743
	return base64_decode($vdb);
744
}
745

    
746
function voucher_unlink_db($roll) {
747
	global $g, $cpzone;
748
	@unlink("{$g['vardb_path']}/voucher_{$cpzone}_used_$roll.db");
749
	@unlink("{$g['vardb_path']}/voucher_{$cpzone}_active_$roll.db");
750
}
751

    
752
/* we share the log with captiveportal for now */
753
function voucher_log($priority, $message) {
754

    
755
	$message = trim($message);
756
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
757
	syslog($priority, sprintf(gettext("Voucher: %s"), $message));
758
	closelog();
759
}
760

    
761
/* Save active and used voucher DB into XML config and write it to flash
762
 * Called during reboot -> system_reboot_cleanup() and every active voucher change
763
 */
764
function voucher_save_db_to_config() {
765
	global $config, $g, $cpzone;
766

    
767
	if (is_array($config['voucher'])) {
768
		foreach ($config['voucher'] as $voucherzone => $vcfg) {
769
			$cpzone = $voucherzone;
770
			voucher_save_db_to_config_zone();
771
		}
772
	}
773
}
774

    
775
function voucher_save_db_to_config_zone() {
776
	global $config, $g, $cpzone;
777

    
778
	if (!isset($config['voucher'][$cpzone]['enable'])) {
779
		return;   // no vouchers or don't want to save DB's
780
	}
781

    
782
	if (!is_array($config['voucher'][$cpzone]['roll'])) {
783
		return;
784
	}
785

    
786
	$voucherlck = lock("voucher{$cpzone}", LOCK_EX);
787

    
788
	// walk all active rolls and save runtime DB's to flash
789
	$a_roll = &$config['voucher'][$cpzone]['roll'];
790
	while (list($key, $value) = each($a_roll)) {
791
		$rollent = &$a_roll[$key];
792
		$roll = $rollent['number'];
793
		$bitmask = voucher_read_used_db($roll);
794
		$rollent['used'] = base64_encode($bitmask);
795
		$active_vouchers = voucher_read_active_db($roll);
796
		$db = array();
797
		$dbi = 1;
798
		foreach ($active_vouchers as $voucher => $line) {
799
			list($timestamp, $minutes) = explode(",", $line);
800
			$activent['voucher'] = $voucher;
801
			$activent['timestamp'] = $timestamp;
802
			$activent['minutes'] = $minutes;
803
			$db["v{$dbi}"] = $activent;
804
			$dbi++;
805
		}
806
		$rollent['active'] = $db;
807
		unset($active_vouchers);
808
	}
809

    
810
	unlock($voucherlck);
811

    
812
	write_config(gettext("Syncing vouchers"));
813
	return;
814
}
815

    
816
?>
(56-56/65)