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
			}
594
			voucher_write_used_db($roll, $rollent['used']);
595
			$minutes = $rollent['minutes'];
596
			$active_vouchers = array();
597
			$a_active = &$rollent['active'];
598
			if (is_array($a_active)) {
599
				foreach ($a_active as $activent) {
600
					$voucher = $activent['voucher'];
601
					$timestamp = $activent['timestamp'];
602
					$minutes = $activent['minutes'];
603
					// its tempting to check for expired timestamps, but during
604
					// bootup, we most likely don't have the correct time.
605
					$active_vouchers[$voucher] = "$timestamp,$minutes";
606
				}
607
			}
608
			voucher_write_active_db($roll, $active_vouchers);
609
		}
610

    
611
		unlock($voucherlck);
612
	}
613

    
614
	return 0;
615
}
616

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

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

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

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

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

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

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

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

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

    
700
	return $used;
701
}
702

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

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

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

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

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

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

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

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

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

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

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

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

    
784
	unlock($voucherlck);
785

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

    
790
?>
(56-56/65)