Project

General

Profile

Download (25.7 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) < 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) && !empty($cpentry[0])) {
295
					if (empty($cpzoneid) && !empty($config['captiveportal'][$cpzone])) {
296
						$cpzoneid = $config['captiveportal'][$cpzone]['zoneid'];
297
					}
298
					$cpentry = $cpentry[0];
299
					captiveportal_disconnect($cpentry, null, 13);
300
					captiveportal_logportalauth($cpentry[4], $cpentry[3], $cpentry[2], "FORCLY TERMINATING VOUCHER {$voucher} SESSION");
301
					$unsetindexes[] = $cpentry[5];
302
				}
303
			} else {
304
				captiveportal_syslog("$voucher ($roll/$nr): not found on any registered Roll");
305
			}
306
		} else {
307
			// hmm, thats weird ... not what I expected
308
			captiveportal_syslog("$voucher invalid: {$output[0]}!!");
309
		}
310
	}
311

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

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

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

    
337
	unlock($voucherlck);
338

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

    
344
	return true;
345
}
346

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

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

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

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

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

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

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

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

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

    
461
		return $test_result;
462
	}
463

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

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

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

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

    
509
	$active_vouchers[$first_voucher_roll][$first_voucher] = "$timestamp,$minutes";
510
	voucher_write_active_db($first_voucher_roll, $active_vouchers[$first_voucher_roll]);
511

    
512
	/* Trigger a sync of the vouchers on config */
513
	send_event("service sync vouchers");
514

    
515
	unlock($voucherlck);
516

    
517
	return $total_minutes;
518
}
519

    
520
function voucher_configure($sync = false) {
521
	global $config, $g, $cpzone;
522

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

    
541
function voucher_configure_zone($sync = false) {
542
	global $config, $g, $cpzone;
543

    
544
	if (!isset($config['voucher'][$cpzone]['enable'])) {
545
		return 0;
546
	}
547

    
548
	if ($sync == true) {
549
		captiveportal_syslog("Writing voucher db from sync data...");
550
	}
551

    
552
	$voucherlck = lock("voucher{$cpzone}", LOCK_EX);
553

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

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

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

    
580
		$voucherlck = lock("voucher{$cpzone}", LOCK_EX);
581

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

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

    
603
		unlock($voucherlck);
604
	}
605

    
606
	return 0;
607
}
608

    
609
/* write bitstring of used vouchers to ramdisk.
610
 * Bitstring must already be base64_encoded!
611
 */
612
function voucher_write_used_db($roll, $vdb) {
613
	global $g, $cpzone;
614

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

    
624
/* return assoc array of active vouchers with activation timestamp
625
 * voucher is index.
626
 */
627
function voucher_read_active_db($roll) {
628
	global $g, $cpzone;
629

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

    
651
				/* Trigger a sync of the vouchers on config */
652
				send_event("service sync vouchers");
653
			}
654
		}
655
	}
656
	return $active;
657
}
658

    
659
/* store array of active vouchers back to DB */
660
function voucher_write_active_db($roll, $active) {
661
	global $g, $cpzone;
662

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

    
675
/* return how many vouchers are marked used on a roll */
676
function voucher_used_count($roll) {
677
	global $g, $cpzone;
678

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

    
692
	return $used;
693
}
694

    
695
function voucher_read_used_db($roll) {
696
	global $g, $cpzone;
697

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

    
712
function voucher_unlink_db($roll) {
713
	global $g, $cpzone;
714
	@unlink("{$g['vardb_path']}/voucher_{$cpzone}_used_$roll.db");
715
	@unlink("{$g['vardb_path']}/voucher_{$cpzone}_active_$roll.db");
716
}
717

    
718
/* we share the log with captiveportal for now */
719
function voucher_log($priority, $message) {
720

    
721
	$message = trim($message);
722
	openlog("logportalauth", LOG_PID, LOG_LOCAL4);
723
	syslog($priority, sprintf(gettext("Voucher: %s"), $message));
724
	closelog();
725
}
726

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

    
733
	if (is_array($config['voucher'])) {
734
		foreach ($config['voucher'] as $voucherzone => $vcfg) {
735
			$cpzone = $voucherzone;
736
			voucher_save_db_to_config_zone();
737
		}
738
	}
739
}
740

    
741
function voucher_save_db_to_config_zone() {
742
	global $config, $g, $cpzone;
743

    
744
	if (!isset($config['voucher'][$cpzone]['enable'])) {
745
		return;   // no vouchers or don't want to save DB's
746
	}
747

    
748
	if (!is_array($config['voucher'][$cpzone]['roll'])) {
749
		return;
750
	}
751

    
752
	$voucherlck = lock("voucher{$cpzone}", LOCK_EX);
753

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

    
776
	unlock($voucherlck);
777

    
778
	write_config("Syncing vouchers");
779
	return;
780
}
781

    
782
?>
(57-57/67)