Project

General

Profile

Download (27 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("Captive Portal Voucher XMLRPC sync data {$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 = "A communications error occurred while attempting CaptivePortalVoucherSync XMLRPC sync with {$url}:{$port} (pfsense.exec_php).";
103
		log_error($error);
104
		file_notice("CaptivePortalVoucherSync", $error, "Communications error occurred", "");
105
		return false;
106
	} elseif ($resp->faultCode()) {
107
		$error = "An error code was received while attempting CaptivePortalVoucherSync XMLRPC sync with {$url}:{$port} - Code " . $resp->faultCode() . ": " . $resp->faultString();
108
		log_error($error);
109
		file_notice("CaptivePortalVoucherSync", $error, "Error code received", "");
110
		return false;
111
	} else {
112
		log_error("CaptivePortalVoucherSync XMLRPC reload data success with {$url}:{$port} (pfsense.exec_php).");
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("Captive Portal Voucher XMLRPC sync data {$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 = "A communications error occurred while attempting CaptivePortalVoucherSync XMLRPC sync with {$url}:{$port} (pfsense.exec_php).";
163
		log_error($error);
164
		file_notice("CaptivePortalVoucherSync", $error, "Communications error occurred", "");
165
		return false;
166
	} elseif ($resp->faultCode()) {
167
		$error = "An error code was received while attempting CaptivePortalVoucherSync XMLRPC sync with {$url}:{$port} - Code " . $resp->faultCode() . ": " . $resp->faultString();
168
		log_error($error);
169
		file_notice("CaptivePortalVoucherSync", $error, "Error code received", "");
170
		return false;
171
	} else {
172
		log_error("CaptivePortalVoucherSync XMLRPC reload data success with {$url}:{$port} (pfsense.exec_php).");
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("Captive Portal Voucher XMLRPC sync data {$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 = "A communications error occurred while attempting CaptivePortalVoucherSync XMLRPC sync with {$url}:{$port} (pfsense.exec_php).";
222
		log_error($error);
223
		file_notice("CaptivePortalVoucherSync", $error, "Communications error occurred", "");
224
		return null; // $timeleft
225
	} elseif ($resp->faultCode()) {
226
		$error = "An error code was received while attempting CaptivePortalVoucherSync XMLRPC sync with {$url}:{$port} - Code " . $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("CaptivePortalVoucherSync XMLRPC reload data success with {$url}:{$port} (pfsense.exec_php).");
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("Captive Portal Voucher database synchronized with {$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("$voucher ($roll/$nr): not found on any registered Roll");
328
			}
329
		} else {
330
			// hmm, thats weird ... not what I expected
331
			captiveportal_syslog("$voucher invalid: {$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
			$test_result[] = "{$voucher} invalid: Too short!";
419
			captiveportal_syslog("{$voucher} invalid: Too short!");
420
			$error++;
421
			continue;   // seems too short to be a voucher!
422
		}
423

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

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

    
487
		return $test_result;
488
	}
489

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

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

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

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

    
535
	$active_vouchers[$first_voucher_roll][$first_voucher] = "$timestamp,$minutes";
536
	voucher_write_active_db($first_voucher_roll, $active_vouchers[$first_voucher_roll]);
537

    
538
	/* Trigger a sync of the vouchers on config */
539
	send_event("service sync vouchers");
540

    
541
	unlock($voucherlck);
542

    
543
	return $total_minutes;
544
}
545

    
546
function voucher_configure($sync = false) {
547
	global $config, $g, $cpzone;
548

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

    
567
function voucher_configure_zone($sync = false) {
568
	global $config, $g, $cpzone;
569

    
570
	if (!isset($config['voucher'][$cpzone]['enable'])) {
571
		return 0;
572
	}
573

    
574
	if ($sync == true) {
575
		captiveportal_syslog("Writing voucher db from sync data...");
576
	}
577

    
578
	$voucherlck = lock("voucher{$cpzone}", LOCK_EX);
579

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

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

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

    
606
		$voucherlck = lock("voucher{$cpzone}", LOCK_EX);
607

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

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

    
633
		unlock($voucherlck);
634
	}
635

    
636
	return 0;
637
}
638

    
639
/* write bitstring of used vouchers to ramdisk.
640
 * Bitstring must already be base64_encoded!
641
 */
642
function voucher_write_used_db($roll, $vdb) {
643
	global $g, $cpzone;
644

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

    
654
/* return assoc array of active vouchers with activation timestamp
655
 * voucher is index.
656
 */
657
function voucher_read_active_db($roll) {
658
	global $g, $cpzone;
659

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

    
681
				/* Trigger a sync of the vouchers on config */
682
				send_event("service sync vouchers");
683
			}
684
		}
685
	}
686
	return $active;
687
}
688

    
689
/* store array of active vouchers back to DB */
690
function voucher_write_active_db($roll, $active) {
691
	global $g, $cpzone;
692

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

    
705
/* return how many vouchers are marked used on a roll */
706
function voucher_used_count($roll) {
707
	global $g, $cpzone;
708

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

    
722
	return $used;
723
}
724

    
725
function voucher_read_used_db($roll) {
726
	global $g, $cpzone;
727

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

    
742
function voucher_unlink_db($roll) {
743
	global $g, $cpzone;
744
	@unlink("{$g['vardb_path']}/voucher_{$cpzone}_used_$roll.db");
745
	@unlink("{$g['vardb_path']}/voucher_{$cpzone}_active_$roll.db");
746
}
747

    
748
/* we share the log with captiveportal for now */
749
function voucher_log($priority, $message) {
750

    
751
	$message = trim($message);
752
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
753
	syslog($priority, sprintf(gettext("Voucher: %s"), $message));
754
	closelog();
755
}
756

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

    
763
	if (is_array($config['voucher'])) {
764
		foreach ($config['voucher'] as $voucherzone => $vcfg) {
765
			$cpzone = $voucherzone;
766
			voucher_save_db_to_config_zone();
767
		}
768
	}
769
}
770

    
771
function voucher_save_db_to_config_zone() {
772
	global $config, $g, $cpzone;
773

    
774
	if (!isset($config['voucher'][$cpzone]['enable'])) {
775
		return;   // no vouchers or don't want to save DB's
776
	}
777

    
778
	if (!is_array($config['voucher'][$cpzone]['roll'])) {
779
		return;
780
	}
781

    
782
	$voucherlck = lock("voucher{$cpzone}", LOCK_EX);
783

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

    
806
	unlock($voucherlck);
807

    
808
	write_config("Syncing vouchers");
809
	return;
810
}
811

    
812
?>
(56-56/65)