Project

General

Profile

Download (26.3 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
function xmlrpc_sync_voucher_expire($vouchers, $syncip, $port, $password, $username) {
42
	global $g, $config, $cpzone;
43
	require_once("xmlrpc.inc");
44

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

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

    
63
EOF;
64

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

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

    
90
	$toreturn =  XML_RPC_Decode($resp->value());
91

    
92
	return $toreturn;
93
}
94

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

    
99
	$protocol = "http";
100
	if (is_array($config['system']) && is_array($config['system']['webgui']) && !empty($config['system']['webgui']['protocol']) &&
101
	    $config['system']['webgui']['protocol'] == "https")
102
		$protocol = "https";
103
	if ($protocol == "https" || $port == "443")
104
		$url = "https://{$syncip}";
105
	else
106
		$url = "http://{$syncip}";
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
	if ($protocol == "https" || $port == "443")
162
		$url = "https://{$syncip}";
163
	else
164
		$url = "http://{$syncip}";
165

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

    
178
EOF;
179

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

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

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

    
216
	return $toreturn['timeleft'];
217
}
218

    
219
function voucher_expire($voucher_received) {
220
	global $g, $config, $cpzone, $cpzoneid;
221

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

    
231
	$voucherlck = lock("voucher{$cpzone}", LOCK_EX);
232

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

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

    
248
	// go through all received vouchers, check their valid and extract
249
	// Roll# and Ticket# using the external readvoucher binary
250
	foreach ($a_vouchers_received as $voucher) {
251
		$v = escapeshellarg($voucher);
252
		if (strlen($voucher) < 3)
253
			continue;   // seems too short to be a voucher!
254

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

    
279
				/* Check if this voucher has any active sessions */
280
				$cpentry = captiveportal_read_db("WHERE username = '{$voucher}'");
281
				if (!empty($cpentry) && !empty($cpentry[0])) {
282
					if (empty($cpzoneid) && !empty($config['captiveportal'][$cpzone]))
283
						$cpzoneid = $config['captiveportal'][$cpzone]['zoneid'];
284
					$cpentry = $cpentry[0];
285
					captiveportal_disconnect($cpentry,null,13);
286
					captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"FORCLY TERMINATING VOUCHER {$voucher} SESSION");
287
					$unsetindexes[] = $cpentry[5];
288
				}
289
			} else
290
				captiveportal_syslog("$voucher ($roll/$nr): not found on any registered Roll");
291
		} else
292
			// hmm, thats weird ... not what I expected
293
			captiveportal_syslog("$voucher invalid: {$output[0]}!!");
294
	}
295

    
296
	// Refresh active DBs
297
	if ($active_dirty == true) {
298
		foreach ($active_vouchers as $roll => $active)
299
			voucher_write_active_db($roll, $active);
300
		unset($active_vouchers);
301

    
302
		/* Trigger a sync of the vouchers on config */
303
		send_event("service sync vouchers");
304
	}
305

    
306
	// Write back the used DB's
307
	if (is_array($bitstring)) {
308
		foreach ($bitstring as $roll => $used) {
309
			if(is_array($used)) {
310
				foreach($used as $u)
311
					voucher_write_used_db($roll, base64_encode($u));
312
			} else {
313
				voucher_write_used_db($roll, base64_encode($used));
314
			}
315
		}
316
		unset($bitstring);
317
	}
318

    
319
	unlock($voucherlck);
320

    
321
	/* Write database */
322
	if (!empty($unsetindexes))
323
		captiveportal_remove_entries($unsetindexes);
324

    
325
	return true;
326
}
327

    
328
/* 
329
 * Authenticate a voucher and return the remaining time credit in minutes
330
 * if $test is set, don't mark the voucher as used nor add it to the list
331
 * of active vouchers
332
 * If $test is set, simply test the voucher. Don't change anything
333
 * but return a more verbose error and result message back
334
 */
335
function voucher_auth($voucher_received, $test = 0) {
336
	global $g, $config, $cpzone, $dbc;
337

    
338
	if (!isset($config['voucher'][$cpzone]['enable']))
339
		return 0;
340

    
341
	// XMLRPC Call over to the master Voucher node
342
	if(!empty($config['voucher'][$cpzone]['vouchersyncdbip'])) {
343
		$syncip   = $config['voucher'][$cpzone]['vouchersyncdbip'];
344
		$syncport = $config['voucher'][$cpzone]['vouchersyncport'];
345
		$syncpass = $config['voucher'][$cpzone]['vouchersyncpass'];
346
		$vouchersyncusername = $config['voucher'][$cpzone]['vouchersyncusername'];
347
		$remote_time_used = xmlrpc_sync_used_voucher($voucher_received, $syncip, $syncport, $syncpass, $vouchersyncusername);
348
	}
349

    
350
	$voucherlck = lock("voucher{$cpzone}", LOCK_EX);
351

    
352
	// read rolls into assoc array with rollid as key and minutes as value
353
	$tickets_per_roll = array();
354
	$minutes_per_roll = array();
355
	if (is_array($config['voucher'][$cpzone]['roll'])) {
356
		foreach ($config['voucher'][$cpzone]['roll'] as $rollent) {
357
			$tickets_per_roll[$rollent['number']] = $rollent['count'];
358
			$minutes_per_roll[$rollent['number']] = $rollent['minutes'];
359
		}
360
	}
361

    
362
	// split into an array. Useful for multiple vouchers given
363
	$a_vouchers_received = preg_split("/[\t\n\r ]+/s", $voucher_received); 
364
	$error = 0;
365
	$test_result = array();     // used to display for voucher test option in GUI
366
	$total_minutes = 0;
367
	$first_voucher = "";
368
	$first_voucher_roll = 0;
369

    
370
	// go through all received vouchers, check their valid and extract
371
	// Roll# and Ticket# using the external readvoucher binary
372
	foreach ($a_vouchers_received as $voucher) {
373
		$v = escapeshellarg($voucher);
374
		if (strlen($voucher) < 3)
375
			continue;   // seems too short to be a voucher!
376

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

    
429
	// if this was a test call, we're done. Return the result.
430
	if ($test) {
431
		if ($error) {
432
			$test_result[] = gettext("Access denied!");
433
		} else {
434
			$test_result[] = sprintf(gettext("Access granted for %d Minutes in total."),$total_minutes);
435
		}
436
		unlock($voucherlck);
437

    
438
		return $test_result;
439
	}
440

    
441
	// if we had an error (one of the vouchers is invalid), return 0.
442
	// Discussion: we could return the time remaining for good vouchers, but then
443
	// the user wouldn't know that he used at least one invalid voucher.
444
	if ($error) {
445
		unlock($voucherlck);
446
		if ($total_minutes > 0)     // probably not needed, but want to make sure
447
			$total_minutes = 0;     // we only report -1 (expired) or 0 (no access)
448
		return $total_minutes;       // well, at least one voucher had errors. Say NO ACCESS
449
	}
450

    
451
	// If we did a XMLRPC sync earlier check the timeleft
452
	if (!empty($config['voucher'][$cpzone]['vouchersyncdbip'])) {
453
		if (!is_null($remote_time_used))
454
			$total_minutes = $remote_time_used;
455
		else if ($remote_time_used < $total_minutes) 
456
			$total_minutes -= $remote_time_used;
457
	}
458

    
459
	// All given vouchers were valid and this isn't simply a test.
460
	// Write back the used DB's
461
	if (is_array($bitstring)) {
462
		foreach ($bitstring as $roll => $used) {
463
			if(is_array($used)) {
464
				foreach($used as $u)
465
					voucher_write_used_db($roll, base64_encode($u));
466
			} else {
467
				voucher_write_used_db($roll, base64_encode($used));
468
			}
469
		}
470
	}
471

    
472
	// Active DB: we only add the first voucher if multiple given
473
	// and give that one all the time credit. This allows the user to logout and
474
	// log in later using just the first voucher. It also keeps username limited
475
	// to one voucher and that voucher shows the correct time credit in 'active vouchers'
476
	if (!empty($active_vouchers[$first_voucher_roll][$first_voucher])) {
477
		list($timestamp, $minutes) = explode(",", $active_vouchers[$first_voucher_roll][$first_voucher]);
478
	} else {
479
		$timestamp = time();    // new voucher
480
		$minutes = $total_minutes;
481
	}
482

    
483
	$active_vouchers[$first_voucher_roll][$first_voucher] = "$timestamp,$minutes";
484
	voucher_write_active_db($first_voucher_roll, $active_vouchers[$first_voucher_roll]);
485

    
486
	/* Trigger a sync of the vouchers on config */
487
	send_event("service sync vouchers");
488

    
489
	unlock($voucherlck);
490

    
491
	return $total_minutes;
492
}
493

    
494
function voucher_configure($sync = false) {
495
	global $config, $g, $cpzone;
496

    
497
	if (is_array($config['voucher'])) {
498
		foreach ($config['voucher'] as $voucherzone => $vcfg) {
499
			if (platform_booting())
500
			    echo gettext("Enabling voucher support... ");
501
			$cpzone = $voucherzone;
502
			$error = voucher_configure_zone($sync);
503
			if (platform_booting()) {
504
				if ($error)
505
					echo "error\n";
506
				else
507
					echo "done\n";
508
			}
509
		}
510
	}
511
}
512

    
513
function voucher_configure_zone($sync = false) {
514
	global $config, $g, $cpzone;
515

    
516
	if (!isset($config['voucher'][$cpzone]['enable']))
517
		return 0;
518

    
519
	if ($sync == true)
520
	    captiveportal_syslog("Writing voucher db from sync data...");
521

    
522
	$voucherlck = lock("voucher{$cpzone}", LOCK_EX);
523

    
524
        /* write public key used to verify vouchers */
525
        $pubkey = base64_decode($config['voucher'][$cpzone]['publickey']);
526
        $fd = fopen("{$g['varetc_path']}/voucher_{$cpzone}.public", "w");
527
        if (!$fd) {
528
            captiveportal_syslog("Voucher error: cannot write voucher.public\n");
529
	    unlock($voucherlck);
530
            return 1;
531
        }
532
        fwrite($fd, $pubkey);
533
        fclose($fd);
534
        @chmod("{$g['varetc_path']}/voucher_{$cpzone}.public", 0600);
535

    
536
        /* write config file used by voucher binary to decode vouchers */
537
        $fd = fopen("{$g['varetc_path']}/voucher_{$cpzone}.cfg", "w");
538
        if (!$fd) {
539
	    printf(gettext("Error: cannot write voucher.cfg") . "\n");
540
	    unlock($voucherlck);
541
            return 1;
542
        }
543
        fwrite($fd, "{$config['voucher'][$cpzone]['rollbits']},{$config['voucher'][$cpzone]['ticketbits']},{$config['voucher'][$cpzone]['checksumbits']},{$config['voucher'][$cpzone]['magic']},{$config['voucher'][$cpzone]['charset']}\n");
544
        fclose($fd);
545
        @chmod("{$g['varetc_path']}/voucher_{$cpzone}.cfg", 0600);
546
	unlock($voucherlck);
547

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

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

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

    
555
                $roll = $rollent['number'];
556
                voucher_write_used_db($roll, $rollent['used']);
557
                $minutes = $rollent['minutes'];
558
                $active_vouchers = array();
559
                $a_active = &$rollent['active'];
560
                if (is_array($a_active)) {
561
                    foreach ($a_active as $activent) {
562
                        $voucher = $activent['voucher'];
563
                        $timestamp = $activent['timestamp'];
564
                        $minutes = $activent['minutes'];
565
                        // its tempting to check for expired timestamps, but during
566
                        // bootup, we most likely don't have the correct time.
567
                        $active_vouchers[$voucher] = "$timestamp,$minutes";
568
                    }
569
                }
570
                voucher_write_active_db($roll, $active_vouchers);
571
            }
572

    
573
		unlock($voucherlck);
574
        }
575

    
576
	return 0;
577
}
578

    
579
/* write bitstring of used vouchers to ramdisk. 
580
 * Bitstring must already be base64_encoded!
581
 */
582
function voucher_write_used_db($roll, $vdb) {
583
	global $g, $cpzone;
584

    
585
	$fd = fopen("{$g['vardb_path']}/voucher_{$cpzone}_used_$roll.db", "w");
586
	if ($fd) {
587
		fwrite($fd, $vdb . "\n");
588
		fclose($fd);
589
	} else
590
		voucher_log(LOG_ERR, sprintf(gettext('cant write %1$s/voucher_%s_used_%2$s.db'), $g['vardb_path'], $cpzone, $roll));
591
}
592

    
593
/* return assoc array of active vouchers with activation timestamp
594
 * voucher is index. 
595
 */
596
function voucher_read_active_db($roll) {
597
	global $g, $cpzone;
598

    
599
	$active = array();
600
	$dirty = 0;
601
	$file = "{$g['vardb_path']}/voucher_{$cpzone}_active_$roll.db";
602
	if (file_exists($file)) {
603
		$fd = fopen($file, "r");
604
		if ($fd) {
605
			while (!feof($fd)) {
606
				$line = trim(fgets($fd));
607
				if ($line) {
608
					list($voucher,$timestamp,$minutes) = explode(",", $line); // voucher,timestamp
609
					if ((($timestamp + (60*$minutes)) - time()) > 0)
610
						$active[$voucher] = "$timestamp,$minutes";
611
					else
612
						$dirty=1;
613
				}
614
			}
615
			fclose($fd);
616
			if ($dirty) { // if we found expired entries, lets save our snapshot
617
				voucher_write_active_db($roll, $active);
618

    
619
				/* Trigger a sync of the vouchers on config */
620
				send_event("service sync vouchers");
621
			}
622
		}
623
	}
624
	return $active;
625
}
626

    
627
/* store array of active vouchers back to DB */
628
function voucher_write_active_db($roll, $active) {
629
    global $g, $cpzone;
630

    
631
	if (!is_array($active))
632
		return;
633
    $fd = fopen("{$g['vardb_path']}/voucher_{$cpzone}_active_$roll.db", "w");
634
    if ($fd) {
635
        foreach($active as $voucher => $value)
636
            fwrite($fd, "$voucher,$value\n");
637
        fclose($fd);
638
    }
639
}
640

    
641
/* return how many vouchers are marked used on a roll */
642
function voucher_used_count($roll) {
643
    global $g, $cpzone;
644

    
645
    $bitstring = voucher_read_used_db($roll);
646
    $max = strlen($bitstring) * 8;
647
    $used = 0;
648
    for ($i = 1; $i <= $max; $i++) {
649
        // check if ticket already used or not. 
650
        $pos = $i >> 3;            // divide by 8 -> octet
651
        $mask = 1 << ($i % 8);  // mask to test bit in octet
652
        if (ord($bitstring[$pos]) & $mask)
653
            $used++;
654
    }
655
    unset($bitstring);
656

    
657
    return $used;
658
}
659

    
660
function voucher_read_used_db($roll) {
661
    global $g, $cpzone;
662

    
663
    $vdb = "";
664
    $file = "{$g['vardb_path']}/voucher_{$cpzone}_used_$roll.db";
665
    if (file_exists($file)) {
666
        $fd = fopen($file, "r");
667
        if ($fd) {
668
            $vdb = trim(fgets($fd));
669
            fclose($fd);
670
        } else {
671
	    voucher_log(LOG_ERR, sprintf(gettext('cant read %1$s/voucher_%s_used_%2$s.db'), $g['vardb_path'], $cpzone, $roll));
672
        }
673
    }
674
    return base64_decode($vdb);
675
}
676

    
677
function voucher_unlink_db($roll) {
678
    global $g, $cpzone;
679
    @unlink("{$g['vardb_path']}/voucher_{$cpzone}_used_$roll.db");
680
    @unlink("{$g['vardb_path']}/voucher_{$cpzone}_active_$roll.db");
681
}
682

    
683
/* we share the log with captiveportal for now */
684
function voucher_log($priority, $message) {
685

    
686
    $message = trim($message);
687
    openlog("logportalauth", LOG_PID, LOG_LOCAL4);
688
    syslog($priority, sprintf(gettext("Voucher: %s"),$message));
689
    closelog();
690
}
691

    
692
/* Save active and used voucher DB into XML config and write it to flash
693
 * Called during reboot -> system_reboot_cleanup() and every active voucher change
694
 */
695
function voucher_save_db_to_config() {
696
    global $config, $g, $cpzone;
697

    
698
	if (is_array($config['voucher'])) {
699
		foreach ($config['voucher'] as $voucherzone => $vcfg) {
700
			$cpzone = $voucherzone;
701
			voucher_save_db_to_config_zone();
702
		}
703
	}
704
}
705

    
706
function voucher_save_db_to_config_zone() {
707
    global $config, $g, $cpzone;
708
    
709
    if (!isset($config['voucher'][$cpzone]['enable']))
710
        return;   // no vouchers or don't want to save DB's
711

    
712
    if (!is_array($config['voucher'][$cpzone]['roll']))
713
	return;
714

    
715
    $voucherlck = lock("voucher{$cpzone}", LOCK_EX);
716

    
717
    // walk all active rolls and save runtime DB's to flash
718
    $a_roll = &$config['voucher'][$cpzone]['roll'];
719
    while (list($key, $value) = each($a_roll)) {
720
        $rollent = &$a_roll[$key];
721
        $roll = $rollent['number'];
722
        $bitmask = voucher_read_used_db($roll);
723
        $rollent['used'] = base64_encode($bitmask);
724
        $active_vouchers = voucher_read_active_db($roll);
725
        $db = array();
726
		$dbi = 1;
727
        foreach($active_vouchers as $voucher => $line) {
728
            list($timestamp,$minutes) = explode(",", $line);
729
            $activent['voucher'] = $voucher;
730
            $activent['timestamp'] = $timestamp;
731
            $activent['minutes'] = $minutes;
732
            $db["v{$dbi}"] = $activent;
733
	    $dbi++;
734
        }
735
        $rollent['active'] = $db;
736
	unset($active_vouchers);
737
    }
738

    
739
    unlock($voucherlck);
740

    
741
    write_config("Syncing vouchers");
742
    return;
743
}
744

    
745
?>
(57-57/67)