Project

General

Profile

Download (26.1 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;
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)) {
282
					captiveportal_disconnect($cpentry,null,13);
283
					captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"FORCLY TERMINATING VOUCHER {$voucher} SESSION");
284
					$unsetindexes[] = $cpentry[5];
285
				}
286
			} else
287
				captiveportal_syslog("$voucher ($roll/$nr): not found on any registered Roll");
288
		} else
289
			// hmm, thats weird ... not what I expected
290
			captiveportal_syslog("$voucher invalid: {$output[0]}!!");
291
	}
292

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

    
299
		/* Trigger a sync of the vouchers on config */
300
		send_event("service sync vouchers");
301
	}
302

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

    
316
	unlock($voucherlck);
317

    
318
	/* Write database */
319
	if (!empty($unsetindexes))
320
		captiveportal_remove_entries($unsetindexes);
321

    
322
	return true;
323
}
324

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

    
335
	if (!isset($config['voucher'][$cpzone]['enable']))
336
		return 0;
337

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

    
347
	$voucherlck = lock("voucher{$cpzone}", LOCK_EX);
348

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

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

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

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

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

    
435
		return $test_result;
436
	}
437

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

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

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

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

    
480
	$active_vouchers[$first_voucher_roll][$first_voucher] = "$timestamp,$minutes";
481
	voucher_write_active_db($first_voucher_roll, $active_vouchers[$first_voucher_roll]);
482

    
483
	/* Trigger a sync of the vouchers on config */
484
	send_event("service sync vouchers");
485

    
486
	unlock($voucherlck);
487

    
488
	return $total_minutes;
489
}
490

    
491
function voucher_configure($sync = false) {
492
	global $config, $g, $cpzone;
493

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

    
510
function voucher_configure_zone($sync = false) {
511
	global $config, $g, $cpzone;
512

    
513
	if (!isset($config['voucher'][$cpzone]['enable']))
514
		return 0;
515

    
516
	if ($sync == true)
517
	    captiveportal_syslog("Writing voucher db from sync data...");
518

    
519
	$voucherlck = lock("voucher{$cpzone}", LOCK_EX);
520

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

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

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

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

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

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

    
570
		unlock($voucherlck);
571
        }
572

    
573
	return 0;
574
}
575

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

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

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

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

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

    
624
/* store array of active vouchers back to DB */
625
function voucher_write_active_db($roll, $active) {
626
    global $g, $cpzone;
627

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

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

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

    
654
    return $used;
655
}
656

    
657
function voucher_read_used_db($roll) {
658
    global $g, $cpzone;
659

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

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

    
680
/* we share the log with captiveportal for now */
681
function voucher_log($priority, $message) {
682

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

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

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

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

    
709
    if (!is_array($config['voucher'][$cpzone]['roll']))
710
	return;
711

    
712
    $voucherlck = lock("voucher{$cpzone}", LOCK_EX);
713

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

    
736
    unlock($voucherlck);
737

    
738
    write_config("Syncing vouchers");
739
    return;
740
}
741

    
742
?>
(58-58/68)