Project

General

Profile

Download (26 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
/* include all configuration functions */
33
if (!function_exists('captiveportal_syslog')) {
34
	require_once("captiveportal.inc");
35
}
36

    
37
function xmlrpc_sync_voucher_expire($vouchers, $syncip, $port, $password, $username) {
38
	global $g, $config, $cpzone;
39
	require_once("xmlrpc.inc");
40

    
41
	$protocol = "http";
42
	if (is_array($config['system']) && is_array($config['system']['webgui']) && !empty($config['system']['webgui']['protocol']) &&
43
	    $config['system']['webgui']['protocol'] == "https") {
44
		$protocol = "https";
45
	}
46
	if ($protocol == "https" || $port == "443") {
47
		$url = "https://{$syncip}";
48
	} else {
49
		$url = "http://{$syncip}";
50
	}
51

    
52
	/* Construct code that is run on remote machine */
53
	$method = 'pfsense.exec_php';
54
	$execcmd = <<<EOF
55
	global \$cpzone;
56
	require_once('/etc/inc/captiveportal.inc');
57
	require_once('/etc/inc/voucher.inc');
58
	\$cpzone = "$cpzone";
59
	voucher_expire("$vouchers");
60

    
61
EOF;
62

    
63
	/* assemble xmlrpc payload */
64
	$params = array(
65
		XML_RPC_encode($password),
66
		XML_RPC_encode($execcmd)
67
	);
68

    
69
	log_error("Captive Portal Voucher XMLRPC sync data {$url}:{$port}.");
70
	$msg = new XML_RPC_Message($method, $params);
71
	$cli = new XML_RPC_Client('/xmlrpc.php', $url, $port);
72
	$cli->setCredentials($username, $password);
73
	$resp = $cli->send($msg, "250");
74
	if (!is_object($resp)) {
75
		$error = "A communications error occurred while attempting CaptivePortalVoucherSync XMLRPC sync with {$url}:{$port} (pfsense.exec_php).";
76
		log_error($error);
77
		file_notice("CaptivePortalVoucherSync", $error, "Communications error occurred", "");
78
		return false;
79
	} elseif ($resp->faultCode()) {
80
		$error = "An error code was received while attempting CaptivePortalVoucherSync XMLRPC sync with {$url}:{$port} - Code " . $resp->faultCode() . ": " . $resp->faultString();
81
		log_error($error);
82
		file_notice("CaptivePortalVoucherSync", $error, "Error code received", "");
83
		return false;
84
	} else {
85
		log_error("CaptivePortalVoucherSync XMLRPC reload data success with {$url}:{$port} (pfsense.exec_php).");
86
	}
87

    
88
	$toreturn = XML_RPC_Decode($resp->value());
89

    
90
	return $toreturn;
91
}
92

    
93
function xmlrpc_sync_voucher_disconnect($dbent, $syncip, $port, $password, $username, $term_cause = 1, $stop_time = null) {
94
	global $g, $config, $cpzone;
95
	require_once("xmlrpc.inc");
96

    
97
	$protocol = "http";
98
	if (is_array($config['system']) && is_array($config['system']['webgui']) && !empty($config['system']['webgui']['protocol']) &&
99
	    $config['system']['webgui']['protocol'] == "https") {
100
		$protocol = "https";
101
	}
102
	if ($protocol == "https" || $port == "443") {
103
		$url = "https://{$syncip}";
104
	} else {
105
		$url = "http://{$syncip}";
106
	}
107

    
108
	/* Construct code that is run on remote machine */
109
	$dbent_str = serialize($dbent);
110
	$tmp_stop_time = (isset($stop_time)) ? $stop_time : "null";
111
	$method = 'pfsense.exec_php';
112
	$execcmd = <<<EOF
113
	global \$cpzone;
114
	require_once('/etc/inc/captiveportal.inc');
115
	require_once('/etc/inc/voucher.inc');
116
	\$cpzone = "$cpzone";
117
	\$radiusservers = captiveportal_get_radius_servers();
118
	\$dbent = unserialize("$dbent_str");
119
	captiveportal_disconnect(\$dbent, \$radiusservers, $term_cause, $tmp_stop_time);
120

    
121
EOF;
122

    
123
	/* assemble xmlrpc payload */
124
	$params = array(
125
		XML_RPC_encode($password),
126
		XML_RPC_encode($execcmd)
127
	);
128

    
129
	log_error("Captive Portal Voucher XMLRPC sync data {$url}:{$port}.");
130
	$msg = new XML_RPC_Message($method, $params);
131
	$cli = new XML_RPC_Client('/xmlrpc.php', $url, $port);
132
	$cli->setCredentials($username, $password);
133
	$resp = $cli->send($msg, "250");
134
	if (!is_object($resp)) {
135
		$error = "A communications error occurred while attempting CaptivePortalVoucherSync XMLRPC sync with {$url}:{$port} (pfsense.exec_php).";
136
		log_error($error);
137
		file_notice("CaptivePortalVoucherSync", $error, "Communications error occurred", "");
138
		return false;
139
	} elseif ($resp->faultCode()) {
140
		$error = "An error code was received while attempting CaptivePortalVoucherSync XMLRPC sync with {$url}:{$port} - Code " . $resp->faultCode() . ": " . $resp->faultString();
141
		log_error($error);
142
		file_notice("CaptivePortalVoucherSync", $error, "Error code received", "");
143
		return false;
144
	} else {
145
		log_error("CaptivePortalVoucherSync XMLRPC reload data success with {$url}:{$port} (pfsense.exec_php).");
146
	}
147

    
148
	$toreturn = XML_RPC_Decode($resp->value());
149

    
150
	return $toreturn;
151
}
152

    
153
function xmlrpc_sync_used_voucher($voucher_received, $syncip, $port, $password, $username) {
154
	global $g, $config, $cpzone;
155
	require_once("xmlrpc.inc");
156

    
157
	$protocol = "http";
158
	if (is_array($config['system']) && is_array($config['system']['webgui']) && !empty($config['system']['webgui']['protocol']) &&
159
	    $config['system']['webgui']['protocol'] == "https") {
160
		$protocol = "https";
161
	}
162
	if ($protocol == "https" || $port == "443") {
163
		$url = "https://{$syncip}";
164
	} else {
165
		$url = "http://{$syncip}";
166
	}
167

    
168
	/* Construct code that is run on remote machine */
169
	$method = 'pfsense.exec_php';
170
	$execcmd = <<<EOF
171
	global \$cpzone;
172
	require_once('/etc/inc/voucher.inc');
173
	\$cpzone = "$cpzone";
174
	\$timeleft = voucher_auth("$voucher_received");
175
	\$toreturn = array();
176
	\$toreturn['timeleft'] = \$timeleft;
177
	\$toreturn['voucher'] = array();
178
	\$toreturn['voucher']['roll'] = \$config['voucher'][\$cpzone]['roll'];
179

    
180
EOF;
181

    
182
	/* assemble xmlrpc payload */
183
	$params = array(
184
		XML_RPC_encode($password),
185
		XML_RPC_encode($execcmd)
186
	);
187

    
188
	log_error("Captive Portal Voucher XMLRPC sync data {$url}:{$port}.");
189
	$msg = new XML_RPC_Message($method, $params);
190
	$cli = new XML_RPC_Client('/xmlrpc.php', $url, $port);
191
	$cli->setCredentials($username, $password);
192
	$resp = $cli->send($msg, "250");
193
	if (!is_object($resp)) {
194
		$error = "A communications error occurred while attempting CaptivePortalVoucherSync XMLRPC sync with {$url}:{$port} (pfsense.exec_php).";
195
		log_error($error);
196
		file_notice("CaptivePortalVoucherSync", $error, "Communications error occurred", "");
197
		return null; // $timeleft
198
	} elseif ($resp->faultCode()) {
199
		$error = "An error code was received while attempting CaptivePortalVoucherSync XMLRPC sync with {$url}:{$port} - Code " . $resp->faultCode() . ": " . $resp->faultString();
200
		log_error($error);
201
		file_notice("CaptivePortalVoucherSync", $error, "Error code received", "");
202
		return null; // $timeleft
203
	} else {
204
		log_error("CaptivePortalVoucherSync XMLRPC reload data success with {$url}:{$port} (pfsense.exec_php).");
205
	}
206
	$toreturn = XML_RPC_Decode($resp->value());
207
	if (!is_array($config['voucher'])) {
208
		$config['voucher'] = array();
209
	}
210

    
211
	if (is_array($toreturn['voucher']) && is_array($toreturn['voucher']['roll'])) {
212
		$config['voucher'][$cpzone]['roll'] = $toreturn['voucher']['roll'];
213
		write_config("Captive Portal Voucher database synchronized with {$url}");
214
		voucher_configure_zone(true);
215
		unset($toreturn['voucher']);
216
	} else if (!isset($toreturn['timeleft'])) {
217
		return null;
218
	}
219

    
220
	return $toreturn['timeleft'];
221
}
222

    
223
function voucher_expire($voucher_received) {
224
	global $g, $config, $cpzone, $cpzoneid;
225

    
226
	// XMLRPC Call over to the master Voucher node
227
	if (!empty($config['voucher'][$cpzone]['vouchersyncdbip'])) {
228
		$syncip   = $config['voucher'][$cpzone]['vouchersyncdbip'];
229
		$syncport = $config['voucher'][$cpzone]['vouchersyncport'];
230
		$syncpass = $config['voucher'][$cpzone]['vouchersyncpass'];
231
		$vouchersyncusername = $config['voucher'][$cpzone]['vouchersyncusername'];
232
		xmlrpc_sync_voucher_expire($voucher_received, $syncip, $syncport, $syncpass, $vouchersyncusername);
233
	}
234

    
235
	$voucherlck = lock("voucher{$cpzone}", LOCK_EX);
236

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

    
247
	// split into an array. Useful for multiple vouchers given
248
	$a_vouchers_received = preg_split("/[\t\n\r ]+/s", $voucher_received);
249
	$active_dirty = false;
250
	$unsetindexes = array();
251

    
252
	// go through all received vouchers, check their valid and extract
253
	// Roll# and Ticket# using the external readvoucher binary
254
	foreach ($a_vouchers_received as $voucher) {
255
		$v = escapeshellarg($voucher);
256
		if (strlen($voucher) < 5) {
257
			captiveportal_syslog("${voucher} invalid: Too short!");
258
			continue;   // seems too short to be a voucher!
259
		}
260

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

    
288
				/* Check if this voucher has any active sessions */
289
				$cpentry = captiveportal_read_db("WHERE username = '{$voucher}'");
290
				if (!empty($cpentry) && !empty($cpentry[0])) {
291
					if (empty($cpzoneid) && !empty($config['captiveportal'][$cpzone])) {
292
						$cpzoneid = $config['captiveportal'][$cpzone]['zoneid'];
293
					}
294
					$cpentry = $cpentry[0];
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) < 5) {
391
			$test_result[] = "{$voucher} invalid: Too short!";
392
			captiveportal_syslog("{$voucher} invalid: Too short!");
393
			$error++;
394
			continue;   // seems too short to be a voucher!
395
		}
396

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

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

    
460
		return $test_result;
461
	}
462

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

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

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

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

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

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

    
514
	unlock($voucherlck);
515

    
516
	return $total_minutes;
517
}
518

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

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

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

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

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

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

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

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

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

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

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

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

    
606
		unlock($voucherlck);
607
	}
608

    
609
	return 0;
610
}
611

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

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

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

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

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

    
662
/* store array of active vouchers back to DB */
663
function voucher_write_active_db($roll, $active) {
664
	global $g, $cpzone;
665

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

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

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

    
695
	return $used;
696
}
697

    
698
function voucher_read_used_db($roll) {
699
	global $g, $cpzone;
700

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

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

    
721
/* we share the log with captiveportal for now */
722
function voucher_log($priority, $message) {
723

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

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

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

    
744
function voucher_save_db_to_config_zone() {
745
	global $config, $g, $cpzone;
746

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

    
751
	if (!is_array($config['voucher'][$cpzone]['roll'])) {
752
		return;
753
	}
754

    
755
	$voucherlck = lock("voucher{$cpzone}", LOCK_EX);
756

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

    
779
	unlock($voucherlck);
780

    
781
	write_config("Syncing vouchers");
782
	return;
783
}
784

    
785
?>
(56-56/65)