Project

General

Profile

Download (25.9 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php
2
/*
3
	Copyright (C) 2010-2012 Ermal Luci <eri@pfsense.org>
4
	Copyright (C) 2010 Scott Ullrich <sullrich@gmail.com>
5
    Copyright (C) 2007 Marcel Wiget <mwiget@mac.com>
6
    All rights reserved.
7
    
8
    Redistribution and use in source and binary forms, with or without
9
    modification, are permitted provided that the following conditions are met:
10
    
11
    1. Redistributions of source code must retain the above copyright notice,
12
       this list of conditions and the following disclaimer.
13
    
14
    2. Redistributions in binary form must reproduce the above copyright
15
       notice, this list of conditions and the following disclaimer in the
16
       documentation and/or other materials provided with the distribution.
17
    
18
    THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
19
    INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
20
    AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
21
    AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
22
    OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23
    SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24
    INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25
    CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26
    ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
    POSSIBILITY OF SUCH DAMAGE.
28

    
29
*/
30

    
31
/*
32
	pfSense_BUILDER_BINARIES:	/usr/local/bin/voucher
33
	pfSense_MODULE:	captiveportal
34
*/
35

    
36
/* include all configuration functions */
37
if(!function_exists('captiveportal_syslog'))
38
	require_once("captiveportal.inc");
39

    
40
function xmlrpc_sync_voucher_expire($vouchers, $syncip, $port, $password, $username) {
41
	global $g, $config, $cpzone;
42
	require_once("xmlrpc.inc");
43

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

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

    
62
EOF;
63

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

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

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

    
91
	return $toreturn;
92
}
93

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

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

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

    
120
EOF;
121

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

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

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

    
149
	return $toreturn;
150
}
151

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

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

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

    
177
EOF;
178

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

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

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

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

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

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

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

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

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

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

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

    
277
				/* Check if this voucher has any active sessions */
278
				$cpentry = captiveportal_read_db("WHERE username = '{$voucher}'");
279
				if (!empty($cpentry)) {
280
					captiveportal_disconnect($cpentry,null,13);
281
					captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"FORCLY TERMINATING VOUCHER {$voucher} SESSION");
282
					$unsetindexes[] = $cpentry[5];
283
				}
284
			} else
285
				captiveportal_syslog("$voucher ($roll/$nr): not found on any registererd Roll");
286
		} else
287
			// hmm, thats weird ... not what I expected
288
			captiveportal_syslog("$voucher invalid: $result !!");
289
	}
290

    
291
	// Refresh active DBs
292
	if ($active_dirty == true) {
293
		foreach ($active_vouchers as $roll => $active)
294
			voucher_write_active_db($roll, $active);
295

    
296
		/* Triger a sync of the vouchers on config */
297
		send_event("service sync vouchers");
298
	}
299

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

    
312
	unlock($voucherlck);
313

    
314
	/* Write database */
315
	if (!empty($unsetindexes))
316
		captiveportal_remove_entries($unsetindexes);
317

    
318
	return true;
319
}
320

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

    
331
	if (!isset($config['voucher'][$cpzone]['enable']))
332
		return 0;
333

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

    
343
	$voucherlck = lock("voucher{$cpzone}", LOCK_EX);
344

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

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

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

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

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

    
431
		return $test_result;
432
	}
433

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

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

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

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

    
476
	$active_vouchers[$first_voucher_roll][$first_voucher] = "$timestamp,$minutes";
477
	voucher_write_active_db($first_voucher_roll, $active_vouchers[$first_voucher_roll]);
478

    
479
	/* Triger a sync of the vouchers on config */
480
	send_event("service sync vouchers");
481

    
482
	unlock($voucherlck);
483

    
484
	return $total_minutes;
485
}
486

    
487
function voucher_configure($sync = false) {
488
	global $config, $g, $cpzone;
489

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

    
506
function voucher_configure_zone($sync = false) {
507
	global $config, $g, $cpzone;
508

    
509
	if (!isset($config['voucher'][$cpzone]['enable']))
510
		return 0;
511

    
512
	if ($sync == true)
513
	    captiveportal_syslog("Writing voucher db from sync data...");
514

    
515
	$voucherlck = lock("voucher{$cpzone}", LOCK_EX);
516

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

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

    
541
        if (($g['booting'] || $sync == true) && is_array($config['voucher'][$cpzone]['roll'])) {
542

    
543
		$voucherlck = lock("voucher{$cpzone}", LOCK_EX);
544

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

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

    
566
		unlock($voucherlck);
567
        }
568

    
569
	return 0;
570
}
571

    
572
/* write bitstring of used vouchers to ramdisk. 
573
 * Bitstring must already be base64_encoded!
574
 */
575
function voucher_write_used_db($roll, $vdb) {
576
	global $g, $cpzone;
577

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

    
586
/* return assoc array of active vouchers with activation timestamp
587
 * voucher is index. 
588
 */
589
function voucher_read_active_db($roll) {
590
	global $g, $cpzone;
591

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

    
612
				/* Triger a sync of the vouchers on config */
613
				send_event("service sync vouchers");
614
			}
615
		}
616
	}
617
	return $active;
618
}
619

    
620
/* store array of active vouchers back to DB */
621
function voucher_write_active_db($roll, $active) {
622
    global $g, $cpzone;
623

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

    
634
/* return how many vouchers are marked used on a roll */
635
function voucher_used_count($roll) {
636
    global $g, $cpzone;
637

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

    
651
function voucher_read_used_db($roll) {
652
    global $g, $cpzone;
653

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

    
668
function voucher_unlink_db($roll) {
669
    global $g, $cpzone;
670
    @unlink("{$g['vardb_path']}/voucher_{$cpzone}_used_$roll.db");
671
    @unlink("{$g['vardb_path']}/voucher_{$cpzone}_active_$roll.db");
672
}
673

    
674
/* we share the log with captiveportal for now */
675
function voucher_log($priority, $message) {
676

    
677
    $message = trim($message);
678
    openlog("logportalauth", LOG_PID, LOG_LOCAL4);
679
    syslog($priority, sprintf(gettext("Voucher: %s"),$message));
680
    closelog();
681
}
682

    
683
/* Save active and used voucher DB into XML config and write it to flash
684
 * Called during reboot -> system_reboot_cleanup() and every active voucher change
685
 */
686
function voucher_save_db_to_config() {
687
    global $config, $g, $cpzone;
688

    
689
	if (is_array($config['voucher'])) {
690
		foreach ($config['voucher'] as $voucherzone => $vcfg) {
691
			$cpzone = $voucherzone;
692
			voucher_save_db_to_config_zone();
693
		}
694
	}
695
}
696

    
697
function voucher_save_db_to_config_zone() {
698
    global $config, $g, $cpzone;
699
    
700
    if (!isset($config['voucher'][$cpzone]['enable']))
701
        return;   // no vouchers or don't want to save DB's
702

    
703
    if (!is_array($config['voucher'][$cpzone]['roll']))
704
	return;
705

    
706
    $voucherlck = lock("voucher{$cpzone}", LOCK_EX);
707

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

    
729
    unlock($voucherlck);
730

    
731
    write_config("Synching vouchers");
732
    return;
733
}
734

    
735
?>
(56-56/66)