Project

General

Profile

Download (26.1 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) < 5) {
262
			captiveportal_syslog("${voucher} invalid: Too short!");
263
			continue;   // seems too short to be a voucher!
264
		}
265

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

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

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

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

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

    
338
	unlock($voucherlck);
339

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

    
345
	return true;
346
}
347

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

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

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

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

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

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

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

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

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

    
465
		return $test_result;
466
	}
467

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

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

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

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

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

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

    
519
	unlock($voucherlck);
520

    
521
	return $total_minutes;
522
}
523

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

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

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

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

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

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

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

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

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

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

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

    
589
			$roll = $rollent['number'];
590
			$len = ($rollent['count'] >> 3) + 1;
591
			if (strlen(base64_decode($rollent['used'])) != $len)
592
				$rollent['used'] = base64_encode(str_repeat("\000", $len));
593
			voucher_write_used_db($roll, $rollent['used']);
594
			$minutes = $rollent['minutes'];
595
			$active_vouchers = array();
596
			$a_active = &$rollent['active'];
597
			if (is_array($a_active)) {
598
				foreach ($a_active as $activent) {
599
					$voucher = $activent['voucher'];
600
					$timestamp = $activent['timestamp'];
601
					$minutes = $activent['minutes'];
602
					// its tempting to check for expired timestamps, but during
603
					// bootup, we most likely don't have the correct time.
604
					$active_vouchers[$voucher] = "$timestamp,$minutes";
605
				}
606
			}
607
			voucher_write_active_db($roll, $active_vouchers);
608
		}
609

    
610
		unlock($voucherlck);
611
	}
612

    
613
	return 0;
614
}
615

    
616
/* write bitstring of used vouchers to ramdisk.
617
 * Bitstring must already be base64_encoded!
618
 */
619
function voucher_write_used_db($roll, $vdb) {
620
	global $g, $cpzone;
621

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

    
631
/* return assoc array of active vouchers with activation timestamp
632
 * voucher is index.
633
 */
634
function voucher_read_active_db($roll) {
635
	global $g, $cpzone;
636

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

    
658
				/* Trigger a sync of the vouchers on config */
659
				send_event("service sync vouchers");
660
			}
661
		}
662
	}
663
	return $active;
664
}
665

    
666
/* store array of active vouchers back to DB */
667
function voucher_write_active_db($roll, $active) {
668
	global $g, $cpzone;
669

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

    
682
/* return how many vouchers are marked used on a roll */
683
function voucher_used_count($roll) {
684
	global $g, $cpzone;
685

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

    
699
	return $used;
700
}
701

    
702
function voucher_read_used_db($roll) {
703
	global $g, $cpzone;
704

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

    
719
function voucher_unlink_db($roll) {
720
	global $g, $cpzone;
721
	@unlink("{$g['vardb_path']}/voucher_{$cpzone}_used_$roll.db");
722
	@unlink("{$g['vardb_path']}/voucher_{$cpzone}_active_$roll.db");
723
}
724

    
725
/* we share the log with captiveportal for now */
726
function voucher_log($priority, $message) {
727

    
728
	$message = trim($message);
729
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
730
	syslog($priority, sprintf(gettext("Voucher: %s"), $message));
731
	closelog();
732
}
733

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

    
740
	if (is_array($config['voucher'])) {
741
		foreach ($config['voucher'] as $voucherzone => $vcfg) {
742
			$cpzone = $voucherzone;
743
			voucher_save_db_to_config_zone();
744
		}
745
	}
746
}
747

    
748
function voucher_save_db_to_config_zone() {
749
	global $config, $g, $cpzone;
750

    
751
	if (!isset($config['voucher'][$cpzone]['enable'])) {
752
		return;   // no vouchers or don't want to save DB's
753
	}
754

    
755
	if (!is_array($config['voucher'][$cpzone]['roll'])) {
756
		return;
757
	}
758

    
759
	$voucherlck = lock("voucher{$cpzone}", LOCK_EX);
760

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

    
783
	unlock($voucherlck);
784

    
785
	write_config("Syncing vouchers");
786
	return;
787
}
788

    
789
?>
(58-58/67)