Project

General

Profile

Download (25.5 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php
2
/*
3
	voucher.inc
4
	Copyright (C) 2010-2012 Ermal Luci <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;
230

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

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

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

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

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

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

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

    
308
	// Refresh active DBs
309
	if ($active_dirty == true) {
310
		foreach ($active_vouchers as $roll => $active) {
311
			voucher_write_active_db($roll, $active);
312
		}
313
		unset($active_vouchers);
314

    
315
		/* Trigger a sync of the vouchers on config */
316
		send_event("service sync vouchers");
317
	}
318

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

    
333
	unlock($voucherlck);
334

    
335
	/* Write database */
336
	if (!empty($unsetindexes)) {
337
		captiveportal_remove_entries($unsetindexes);
338
	}
339

    
340
	return true;
341
}
342

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

    
353
	if (!isset($config['voucher'][$cpzone]['enable'])) {
354
		return 0;
355
	}
356

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

    
366
	$voucherlck = lock("voucher{$cpzone}", LOCK_EX);
367

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

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

    
386
	// go through all received vouchers, check their valid and extract
387
	// Roll# and Ticket# using the external readvoucher binary
388
	foreach ($a_vouchers_received as $voucher) {
389
		$v = escapeshellarg($voucher);
390
		if (strlen($voucher) < 3) {
391
			continue;   // seems too short to be a voucher!
392
		}
393

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

    
448
	// if this was a test call, we're done. Return the result.
449
	if ($test) {
450
		if ($error) {
451
			$test_result[] = gettext("Access denied!");
452
		} else {
453
			$test_result[] = sprintf(gettext("Access granted for %d Minutes in total."),$total_minutes);
454
		}
455
		unlock($voucherlck);
456

    
457
		return $test_result;
458
	}
459

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

    
471
	// If we did a XMLRPC sync earlier check the timeleft
472
	if (!empty($config['voucher'][$cpzone]['vouchersyncdbip'])) {
473
		if (!is_null($remote_time_used)) {
474
			$total_minutes = $remote_time_used;
475
		} else if ($remote_time_used < $total_minutes) {
476
			$total_minutes -= $remote_time_used;
477
		}
478
	}
479

    
480
	// All given vouchers were valid and this isn't simply a test.
481
	// Write back the used DB's
482
	if (is_array($bitstring)) {
483
		foreach ($bitstring as $roll => $used) {
484
			if (is_array($used)) {
485
				foreach ($used as $u) {
486
					voucher_write_used_db($roll, base64_encode($u));
487
				}
488
			} else {
489
				voucher_write_used_db($roll, base64_encode($used));
490
			}
491
		}
492
	}
493

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

    
505
	$active_vouchers[$first_voucher_roll][$first_voucher] = "$timestamp,$minutes";
506
	voucher_write_active_db($first_voucher_roll, $active_vouchers[$first_voucher_roll]);
507

    
508
	/* Trigger a sync of the vouchers on config */
509
	send_event("service sync vouchers");
510

    
511
	unlock($voucherlck);
512

    
513
	return $total_minutes;
514
}
515

    
516
function voucher_configure($sync = false) {
517
	global $config, $g, $cpzone;
518

    
519
	if (is_array($config['voucher'])) {
520
		foreach ($config['voucher'] as $voucherzone => $vcfg) {
521
			if (platform_booting()) {
522
				echo gettext("Enabling voucher support... ");
523
			}
524
			$cpzone = $voucherzone;
525
			$error = voucher_configure_zone($sync);
526
			if (platform_booting()) {
527
				if ($error) {
528
					echo "error\n";
529
				} else {
530
					echo "done\n";
531
				}
532
			}
533
		}
534
	}
535
}
536

    
537
function voucher_configure_zone($sync = false) {
538
	global $config, $g, $cpzone;
539

    
540
	if (!isset($config['voucher'][$cpzone]['enable'])) {
541
		return 0;
542
	}
543

    
544
	if ($sync == true) {
545
		captiveportal_syslog("Writing voucher db from sync data...");
546
	}
547

    
548
	$voucherlck = lock("voucher{$cpzone}", LOCK_EX);
549

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

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

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

    
576
		$voucherlck = lock("voucher{$cpzone}", LOCK_EX);
577

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

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

    
599
		unlock($voucherlck);
600
	}
601

    
602
	return 0;
603
}
604

    
605
/* write bitstring of used vouchers to ramdisk.
606
 * Bitstring must already be base64_encoded!
607
 */
608
function voucher_write_used_db($roll, $vdb) {
609
	global $g, $cpzone;
610

    
611
	$fd = fopen("{$g['vardb_path']}/voucher_{$cpzone}_used_$roll.db", "w");
612
	if ($fd) {
613
		fwrite($fd, $vdb . "\n");
614
		fclose($fd);
615
	} else {
616
		voucher_log(LOG_ERR, sprintf(gettext('cant write %1$s/voucher_%s_used_%2$s.db'), $g['vardb_path'], $cpzone, $roll));
617
	}
618
}
619

    
620
/* return assoc array of active vouchers with activation timestamp
621
 * voucher is index.
622
 */
623
function voucher_read_active_db($roll) {
624
	global $g, $cpzone;
625

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

    
647
				/* Trigger a sync of the vouchers on config */
648
				send_event("service sync vouchers");
649
			}
650
		}
651
	}
652
	return $active;
653
}
654

    
655
/* store array of active vouchers back to DB */
656
function voucher_write_active_db($roll, $active) {
657
	global $g, $cpzone;
658

    
659
	if (!is_array($active)) {
660
		return;
661
	}
662
	$fd = fopen("{$g['vardb_path']}/voucher_{$cpzone}_active_$roll.db", "w");
663
	if ($fd) {
664
		foreach ($active as $voucher => $value) {
665
			fwrite($fd, "$voucher,$value\n");
666
		}
667
		fclose($fd);
668
	}
669
}
670

    
671
/* return how many vouchers are marked used on a roll */
672
function voucher_used_count($roll) {
673
	global $g, $cpzone;
674

    
675
	$bitstring = voucher_read_used_db($roll);
676
	$max = strlen($bitstring) * 8;
677
	$used = 0;
678
	for ($i = 1; $i <= $max; $i++) {
679
		// check if ticket already used or not.
680
		$pos = $i >> 3;            // divide by 8 -> octet
681
		$mask = 1 << ($i % 8);  // mask to test bit in octet
682
		if (ord($bitstring[$pos]) & $mask) {
683
			$used++;
684
		}
685
	}
686
	unset($bitstring);
687

    
688
	return $used;
689
}
690

    
691
function voucher_read_used_db($roll) {
692
	global $g, $cpzone;
693

    
694
	$vdb = "";
695
	$file = "{$g['vardb_path']}/voucher_{$cpzone}_used_$roll.db";
696
	if (file_exists($file)) {
697
		$fd = fopen($file, "r");
698
		if ($fd) {
699
			$vdb = trim(fgets($fd));
700
			fclose($fd);
701
		} else {
702
			voucher_log(LOG_ERR, sprintf(gettext('cant read %1$s/voucher_%s_used_%2$s.db'), $g['vardb_path'], $cpzone, $roll));
703
		}
704
	}
705
	return base64_decode($vdb);
706
}
707

    
708
function voucher_unlink_db($roll) {
709
	global $g, $cpzone;
710
	@unlink("{$g['vardb_path']}/voucher_{$cpzone}_used_$roll.db");
711
	@unlink("{$g['vardb_path']}/voucher_{$cpzone}_active_$roll.db");
712
}
713

    
714
/* we share the log with captiveportal for now */
715
function voucher_log($priority, $message) {
716

    
717
	$message = trim($message);
718
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
719
	syslog($priority, sprintf(gettext("Voucher: %s"),$message));
720
	closelog();
721
}
722

    
723
/* Save active and used voucher DB into XML config and write it to flash
724
 * Called during reboot -> system_reboot_cleanup() and every active voucher change
725
 */
726
function voucher_save_db_to_config() {
727
	global $config, $g, $cpzone;
728

    
729
	if (is_array($config['voucher'])) {
730
		foreach ($config['voucher'] as $voucherzone => $vcfg) {
731
			$cpzone = $voucherzone;
732
			voucher_save_db_to_config_zone();
733
		}
734
	}
735
}
736

    
737
function voucher_save_db_to_config_zone() {
738
	global $config, $g, $cpzone;
739

    
740
	if (!isset($config['voucher'][$cpzone]['enable'])) {
741
		return;   // no vouchers or don't want to save DB's
742
	}
743

    
744
	if (!is_array($config['voucher'][$cpzone]['roll'])) {
745
		return;
746
	}
747

    
748
	$voucherlck = lock("voucher{$cpzone}", LOCK_EX);
749

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

    
772
	unlock($voucherlck);
773

    
774
	write_config("Syncing vouchers");
775
	return;
776
}
777

    
778
?>
(58-58/68)