Project

General

Profile

Download (25.2 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
	if ($port == "443") 
44
		$url = "https://{$syncip}";
45
	else if ($port == "80")
46
		$url = "http://{$syncip}";
47
	else
48
		$url = "http://{$syncip}:{$port}";
49

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

    
58
EOF;
59

    
60
	/* assemble xmlrpc payload */
61
	$params = array(
62
		XML_RPC_encode($password),
63
		XML_RPC_encode($execcmd)
64
	);
65

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

    
85
	$toreturn =  XML_RPC_Decode($resp->value());
86

    
87
	return $toreturn;
88
}
89

    
90
function xmlrpc_sync_voucher_disconnect($dbent, $syncip, $port, $password, $username, $term_cause = 1, $stop_time = null) {
91
	global $g, $config, $cpzone;
92
	require_once("xmlrpc.inc");
93
	if ($port == "443") 
94
		$url = "https://{$syncip}";
95
	else if ($port == "80")
96
		$url = "http://{$syncip}";
97
	else
98
		$url = "http://{$syncip}:{$port}";
99

    
100
	/* Construct code that is run on remote machine */
101
	$dbent_str = serialize($dbent);
102
	$tmp_stop_time = (isset($stop_time)) ? $stop_time : "null";
103
	$method = 'pfsense.exec_php';
104
	$execcmd  = <<<EOF
105
	require_once('/etc/inc/captiveportal.inc');
106
	require_once('/etc/inc/voucher.inc');
107
	\$cpzone = "$cpzone";
108
	\$radiusservers = captiveportal_get_radius_servers();
109
	\$dbent = unserialize("$dbent_str");
110
	captiveportal_disconnect(\$dbent, \$radiusservers, $term_cause, $tmp_stop_time);
111

    
112
EOF;
113

    
114
	/* assemble xmlrpc payload */
115
	$params = array(
116
		XML_RPC_encode($password),
117
		XML_RPC_encode($execcmd)
118
	);
119

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

    
139
	$toreturn =  XML_RPC_Decode($resp->value());
140

    
141
	return $toreturn;
142
}
143

    
144
function xmlrpc_sync_used_voucher($voucher_received, $syncip, $port, $password, $username) {
145
	global $g, $config, $cpzone;
146
	require_once("xmlrpc.inc");
147
	if ($port == "443") 
148
		$url = "https://{$syncip}";
149
	else if ($port == "80")
150
		$url = "http://{$syncip}";
151
	else
152
		$url = "http://{$syncip}:{$port}";
153

    
154
	/* Construct code that is run on remote machine */
155
	$method = 'pfsense.exec_php';
156
	$execcmd  = <<<EOF
157
	require_once('/etc/inc/voucher.inc');
158
	\$cpzone = "$cpzone";
159
	\$timeleft = voucher_auth("$voucher_received");
160
	\$toreturn = array();
161
	\$toreturn['timeleft'] = \$timeleft;
162
	\$toreturn['voucher'] = array();
163
	\$toreturn['voucher']['roll'] = \$config['voucher'][\$cpzone]['roll'];
164

    
165
EOF;
166

    
167
	/* assemble xmlrpc payload */
168
	$params = array(
169
		XML_RPC_encode($password),
170
		XML_RPC_encode($execcmd)
171
	);
172

    
173
	log_error("Captive Portal Voucher XMLRPC sync data {$url}:{$port}.");
174
	$msg = new XML_RPC_Message($method, $params);
175
	$cli = new XML_RPC_Client('/xmlrpc.php', $url, $port);
176
	$cli->setCredentials($username, $password);
177
	$resp = $cli->send($msg, "250");
178
	if(!is_object($resp)) {
179
		$error = "A communications error occurred while attempting CaptivePortalVoucherSync XMLRPC sync with {$url}:{$port} (pfsense.exec_php).";
180
		log_error($error);
181
		file_notice("CaptivePortalVoucherSync", $error, "Communications error occurred", "");
182
		return 0; // $timeleft
183
	} elseif($resp->faultCode()) {
184
		$error = "An error code was received while attempting CaptivePortalVoucherSync XMLRPC sync with {$url}:{$port} - Code " . $resp->faultCode() . ": " . $resp->faultString();
185
		log_error($error);
186
		file_notice("CaptivePortalVoucherSync", $error, "Error code received", "");
187
		return 0; // $timeleft
188
	} else {
189
		log_error("CaptivePortalVoucherSync XMLRPC reload data success with {$url}:{$port} (pfsense.exec_php).");
190
	}
191
	$toreturn =  XML_RPC_Decode($resp->value());
192
	if (!is_array($config['voucher']))
193
		$config['voucher'] = array();
194
	if (is_array($toreturn['voucher']) && (count($toreturn['voucher'][$cpzone]['roll']) <> count($config['voucher'][$cpzone]['roll']))) {
195
		$config['voucher'][$cpzone]['roll'] = $toreturn['voucher']['roll'];
196
		write_config("Captive Portal Voucher database synchronized with {$url}");
197
		voucher_configure_zone(true);
198
	}
199

    
200
	return $toreturn['timeleft'];
201
}
202

    
203
function voucher_expire($voucher_received) {
204
	global $g, $config, $cpzone;
205

    
206
	// XMLRPC Call over to the master Voucher node
207
	if(!empty($config['voucher'][$cpzone]['vouchersyncdbip'])) {
208
		$syncip   = $config['voucher'][$cpzone]['vouchersyncdbip'];
209
		$syncport = $config['voucher'][$cpzone]['vouchersyncport'];
210
		$syncpass = $config['voucher'][$cpzone]['vouchersyncpass'];
211
		$vouchersyncusername = $config['voucher'][$cpzone]['vouchersyncusername'];
212
		xmlrpc_sync_voucher_expire($voucher_received, $syncip, $syncport, $syncpass, $vouchersyncusername);
213
	}
214

    
215
	$voucherlck = lock("voucher{$cpzone}", LOCK_EX);
216

    
217
	// read rolls into assoc array with rollid as key and minutes as value
218
	$tickets_per_roll = array();
219
	$minutes_per_roll = array();
220
	if (is_array($config['voucher'][$cpzone]['roll'])) {
221
		foreach ($config['voucher'][$cpzone]['roll'] as $rollent) {
222
			$tickets_per_roll[$rollent['number']] = $rollent['count'];
223
			$minutes_per_roll[$rollent['number']] = $rollent['minutes'];
224
		}
225
	}
226

    
227
	// split into an array. Useful for multiple vouchers given
228
	$a_vouchers_received = preg_split("/[\t\n\r ]+/s", $voucher_received); 
229
	$active_dirty = false;
230
	$unsetindexes = array();
231

    
232
	// go through all received vouchers, check their valid and extract
233
	// Roll# and Ticket# using the external readvoucher binary
234
	foreach ($a_vouchers_received as $voucher) {
235
		$v = escapeshellarg($voucher);
236
		if (strlen($voucher) < 3)
237
			continue;   // seems too short to be a voucher!
238

    
239
		$result = exec("/usr/local/bin/voucher -c {$g['varetc_path']}/voucher_{$cpzone}.cfg -k {$g['varetc_path']}/voucher_{$cpzone}.public -- $v");
240
		list($status, $roll, $nr) = explode(" ", $result);
241
		if ($status == "OK") {
242
			// check if we have this ticket on a registered roll for this ticket 
243
			if ($tickets_per_roll[$roll] && ($nr <= $tickets_per_roll[$roll])) {
244
				// voucher is from a registered roll. 
245
				if (!isset($active_vouchers[$roll]))
246
					$active_vouchers[$roll] = voucher_read_active_db($roll);
247
				// valid voucher. Store roll# and ticket#
248
				if (!empty($active_vouchers[$roll][$voucher])) {
249
					$active_dirty = true;
250
					unset($active_vouchers[$roll][$voucher]);
251
				}
252
				// check if voucher already marked as used
253
				if (!isset($bitstring[$roll]))
254
					$bitstring[$roll] = voucher_read_used_db($roll);
255
				$pos = $nr >> 3; // divide by 8 -> octet
256
				$mask = 1 << ($nr % 8);
257
				// mark bit for this voucher as used
258
				if (!(ord($bitstring[$roll][$pos]) & $mask))
259
					$bitstring[$roll][$pos] = chr(ord($bitstring[$roll][$pos]) | $mask);
260
				captiveportal_syslog("{$voucher} ({$roll}/{$nr}) forced to expire");
261

    
262
				/* Check if this voucher has any active sessions */
263
				$cpentry = captiveportal_read_db("WHERE username = '{$voucher}'");
264
				if (!empty($cpentry)) {
265
					captiveportal_disconnect($cpentry,null,13);
266
					captiveportal_logportalauth($cpentry[4],$cpentry[3],$cpentry[2],"FORCLY TERMINATING VOUCHER {$voucher} SESSION");
267
					$unsetindexes[] = $cpentry[5];
268
				}
269
			} else
270
				captiveportal_syslog("$voucher ($roll/$nr): not found on any registererd Roll");
271
		} else
272
			// hmm, thats weird ... not what I expected
273
			captiveportal_syslog("$voucher invalid: $result !!");
274
	}
275

    
276
	// Refresh active DBs
277
	if ($active_dirty == true) {
278
		foreach ($active_vouchers as $roll => $active)
279
			voucher_write_active_db($roll, $active);
280

    
281
		/* Triger a sync of the vouchers on config */
282
		send_event("service sync vouchers");
283
	}
284

    
285
	// Write back the used DB's
286
	if (is_array($bitstring)) {
287
		foreach ($bitstring as $roll => $used) {
288
			if(is_array($used)) {
289
				foreach($used as $u)
290
					voucher_write_used_db($roll, base64_encode($u));
291
			} else {
292
				voucher_write_used_db($roll, base64_encode($used));
293
			}
294
		}
295
	}
296

    
297
	unlock($voucherlck);
298

    
299
	/* Write database */
300
	if (!empty($unsetindexes))
301
		captiveportal_remove_entries($unsetindexes);
302

    
303
	return true;
304
}
305

    
306
/* 
307
 * Authenticate a voucher and return the remaining time credit in minutes
308
 * if $test is set, don't mark the voucher as used nor add it to the list
309
 * of active vouchers
310
 * If $test is set, simply test the voucher. Don't change anything
311
 * but return a more verbose error and result message back
312
 */
313
function voucher_auth($voucher_received, $test = 0) {
314
	global $g, $config, $cpzone, $dbc;
315

    
316
	if (!isset($config['voucher'][$cpzone]['enable']))
317
		return 0;
318

    
319
	// XMLRPC Call over to the master Voucher node
320
	if(!empty($config['voucher'][$cpzone]['vouchersyncdbip'])) {
321
		$syncip   = $config['voucher'][$cpzone]['vouchersyncdbip'];
322
		$syncport = $config['voucher'][$cpzone]['vouchersyncport'];
323
		$syncpass = $config['voucher'][$cpzone]['vouchersyncpass'];
324
		$vouchersyncusername = $config['voucher'][$cpzone]['vouchersyncusername'];
325
		$remote_time_used = xmlrpc_sync_used_voucher($voucher_received, $syncip, $syncport, $syncpass, $vouchersyncusername);
326
	}
327

    
328
	$voucherlck = lock("voucher{$cpzone}", LOCK_EX);
329

    
330
	// read rolls into assoc array with rollid as key and minutes as value
331
	$tickets_per_roll = array();
332
	$minutes_per_roll = array();
333
	if (is_array($config['voucher'][$cpzone]['roll'])) {
334
		foreach ($config['voucher'][$cpzone]['roll'] as $rollent) {
335
			$tickets_per_roll[$rollent['number']] = $rollent['count'];
336
			$minutes_per_roll[$rollent['number']] = $rollent['minutes'];
337
		}
338
	}
339

    
340
	// split into an array. Useful for multiple vouchers given
341
	$a_vouchers_received = preg_split("/[\t\n\r ]+/s", $voucher_received); 
342
	$error = 0;
343
	$test_result = array();     // used to display for voucher test option in GUI
344
	$total_minutes = 0;
345
	$first_voucher = "";
346
	$first_voucher_roll = 0;
347

    
348
	// go through all received vouchers, check their valid and extract
349
	// Roll# and Ticket# using the external readvoucher binary
350
	foreach ($a_vouchers_received as $voucher) {
351
		$v = escapeshellarg($voucher);
352
		if (strlen($voucher) < 3)
353
			continue;   // seems too short to be a voucher!
354

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

    
407
	// if this was a test call, we're done. Return the result.
408
	if ($test) {
409
		if ($error) {
410
			$test_result[] = gettext("Access denied!");
411
		} else {
412
			$test_result[] = sprintf(gettext("Access granted for %d Minutes in total."),$total_minutes);
413
		}
414
		unlock($voucherlck);
415

    
416
		return $test_result;
417
	}
418

    
419
	// if we had an error (one of the vouchers is invalid), return 0.
420
	// Discussion: we could return the time remaining for good vouchers, but then
421
	// the user wouldn't know that he used at least one invalid voucher.
422
	if ($error) {
423
		unlock($voucherlck);
424
		if ($total_minutes > 0)     // probably not needed, but want to make sure
425
			$total_minutes = 0;     // we only report -1 (expired) or 0 (no access)
426
		return $total_minutes;       // well, at least one voucher had errors. Say NO ACCESS
427
	}
428

    
429
	// If we did a XMLRPC sync earlier check the timeleft
430
	if (!empty($config['voucher'][$cpzone]['vouchersyncdbip'])) 
431
		if($remote_time_used < $total_minutes) 
432
			$total_minutes = $remote_time_used;
433

    
434
	// All given vouchers were valid and this isn't simply a test.
435
	// Write back the used DB's
436
	if (is_array($bitstring)) {
437
		foreach ($bitstring as $roll => $used) {
438
			if(is_array($used)) {
439
				foreach($used as $u)
440
					voucher_write_used_db($roll, base64_encode($u));
441
			} else {
442
				voucher_write_used_db($roll, base64_encode($used));
443
			}
444
		}
445
	}
446

    
447
	// Active DB: we only add the first voucher if multiple given
448
	// and give that one all the time credit. This allows the user to logout and
449
	// log in later using just the first voucher. It also keeps username limited
450
	// to one voucher and that voucher shows the correct time credit in 'active vouchers'
451
	if (!empty($active_vouchers[$first_voucher_roll][$first_voucher])) {
452
		list($timestamp, $minutes) = explode(",", $active_vouchers[$first_voucher_roll][$first_voucher]);
453
	} else {
454
		$timestamp = time();    // new voucher
455
		$minutes = $total_minutes;
456
	}
457

    
458
	$active_vouchers[$first_voucher_roll][$first_voucher] = "$timestamp,$minutes";
459
	voucher_write_active_db($first_voucher_roll, $active_vouchers[$first_voucher_roll]);
460

    
461
	/* Triger a sync of the vouchers on config */
462
	send_event("service sync vouchers");
463

    
464
	unlock($voucherlck);
465

    
466
	return $total_minutes;
467
}
468

    
469
function voucher_configure($sync = false) {
470
	global $config, $g, $cpzone;
471

    
472
	if (is_array($config['voucher'])) {
473
		foreach ($config['voucher'] as $voucherzone => $vcfg) {
474
			if ($g['booting'])
475
			    echo gettext("Enabling voucher support... ");
476
			$cpzone = $voucherzone;
477
			$error = voucher_configure_zone($sync);
478
			if ($g['booting']) {
479
				if ($error)
480
					echo "error\n";
481
				else
482
					echo "done\n";
483
			}
484
		}
485
	}
486
}
487

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

    
491
	if (!isset($config['voucher'][$cpzone]['enable']))
492
		return 0;
493

    
494
	if ($sync == true)
495
	    captiveportal_syslog("Writing voucher db from sync data...");
496

    
497
	$voucherlck = lock("voucher{$cpzone}", LOCK_EX);
498

    
499
        /* write public key used to verify vouchers */
500
        $pubkey = base64_decode($config['voucher'][$cpzone]['publickey']);
501
        $fd = fopen("{$g['varetc_path']}/voucher_{$cpzone}.public", "w");
502
        if (!$fd) {
503
            captiveportal_syslog("Voucher error: cannot write voucher.public\n");
504
	    unlock($voucherlck);
505
            return 1;
506
        }
507
        fwrite($fd, $pubkey);
508
        fclose($fd);
509
        @chmod("{$g['varetc_path']}/voucher_{$cpzone}.public", 0600);
510

    
511
        /* write config file used by voucher binary to decode vouchers */
512
        $fd = fopen("{$g['varetc_path']}/voucher_{$cpzone}.cfg", "w");
513
        if (!$fd) {
514
	    printf(gettext("Error: cannot write voucher.cfg") . "\n");
515
	    unlock($voucherlck);
516
            return 1;
517
        }
518
        fwrite($fd, "{$config['voucher'][$cpzone]['rollbits']},{$config['voucher'][$cpzone]['ticketbits']},{$config['voucher'][$cpzone]['checksumbits']},{$config['voucher'][$cpzone]['magic']},{$config['voucher'][$cpzone]['charset']}\n");
519
        fclose($fd);
520
        @chmod("{$g['varetc_path']}/voucher_{$cpzone}.cfg", 0600);
521
	unlock($voucherlck);
522

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

    
525
		$voucherlck = lock("voucher{$cpzone}", LOCK_EX);
526

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

    
530
                $roll = $rollent['number'];
531
                voucher_write_used_db($roll, $rollent['used']);
532
                $minutes = $rollent['minutes'];
533
                $active_vouchers = array();
534
                $a_active = &$rollent['active'];
535
                if (is_array($a_active)) {
536
                    foreach ($a_active as $activent) {
537
                        $voucher = $activent['voucher'];
538
                        $timestamp = $activent['timestamp'];
539
                        $minutes = $activent['minutes'];
540
                        // its tempting to check for expired timestamps, but during
541
                        // bootup, we most likely don't have the correct time time.
542
                        $active_vouchers[$voucher] = "$timestamp,$minutes";
543
                    }
544
                }
545
                voucher_write_active_db($roll, $active_vouchers);
546
            }
547

    
548
		unlock($voucherlck);
549
        }
550

    
551
	return 0;
552
}
553

    
554
/* write bitstring of used vouchers to ramdisk. 
555
 * Bitstring must already be base64_encoded!
556
 */
557
function voucher_write_used_db($roll, $vdb) {
558
	global $g, $cpzone;
559

    
560
	$fd = fopen("{$g['vardb_path']}/voucher_{$cpzone}_used_$roll.db", "w");
561
	if ($fd) {
562
		fwrite($fd, $vdb . "\n");
563
		fclose($fd);
564
	} else
565
		voucher_log(LOG_ERR, sprintf(gettext('cant write %1$s/voucher_%s_used_%2$s.db'), $g['vardb_path'], $cpzone, $roll));
566
}
567

    
568
/* return assoc array of active vouchers with activation timestamp
569
 * voucher is index. 
570
 */
571
function voucher_read_active_db($roll) {
572
	global $g, $cpzone;
573

    
574
	$active = array();
575
	$dirty = 0;
576
	$file = "{$g['vardb_path']}/voucher_{$cpzone}_active_$roll.db";
577
	if (file_exists($file)) {
578
		$fd = fopen($file, "r");
579
		if ($fd) {
580
			while (!feof($fd)) {
581
				$line = trim(fgets($fd));
582
				if ($line) {
583
					list($voucher,$timestamp,$minutes) = explode(",", $line); // voucher,timestamp
584
					if ((($timestamp + (60*$minutes)) - time()) > 0)
585
						$active[$voucher] = "$timestamp,$minutes";
586
					else
587
						$dirty=1;
588
				}
589
			}
590
			fclose($fd);
591
			if ($dirty) { // if we found expired entries, lets save our snapshot
592
				voucher_write_active_db($roll, $active);
593

    
594
				/* Triger a sync of the vouchers on config */
595
				send_event("service sync vouchers");
596
			}
597
		}
598
	}
599
	return $active;
600
}
601

    
602
/* store array of active vouchers back to DB */
603
function voucher_write_active_db($roll, $active) {
604
    global $g, $cpzone;
605

    
606
	if (!is_array($active))
607
		return;
608
    $fd = fopen("{$g['vardb_path']}/voucher_{$cpzone}_active_$roll.db", "w");
609
    if ($fd) {
610
        foreach($active as $voucher => $value)
611
            fwrite($fd, "$voucher,$value\n");
612
        fclose($fd);
613
    }
614
}
615

    
616
/* return how many vouchers are marked used on a roll */
617
function voucher_used_count($roll) {
618
    global $g, $cpzone;
619

    
620
    $bitstring = voucher_read_used_db($roll);
621
    $max = strlen($bitstring) * 8;
622
    $used = 0;
623
    for ($i = 1; $i <= $max; $i++) {
624
        // check if ticket already used or not. 
625
        $pos = $i >> 3;            // divide by 8 -> octet
626
        $mask = 1 << ($i % 8);  // mask to test bit in octet
627
        if (ord($bitstring[$pos]) & $mask)
628
            $used++;
629
    }   
630
    return $used;
631
}
632

    
633
function voucher_read_used_db($roll) {
634
    global $g, $cpzone;
635

    
636
    $vdb = "";
637
    $file = "{$g['vardb_path']}/voucher_{$cpzone}_used_$roll.db";
638
    if (file_exists($file)) {
639
        $fd = fopen($file, "r");
640
        if ($fd) {
641
            $vdb = trim(fgets($fd));
642
            fclose($fd);
643
        } else {
644
	    voucher_log(LOG_ERR, sprintf(gettext('cant read %1$s/voucher_%s_used_%2$s.db'), $g['vardb_path'], $cpzone, $roll));
645
        }
646
    }
647
    return base64_decode($vdb);
648
}
649

    
650
function voucher_unlink_db($roll) {
651
    global $g, $cpzone;
652
    @unlink("{$g['vardb_path']}/voucher_{$cpzone}_used_$roll.db");
653
    @unlink("{$g['vardb_path']}/voucher_{$cpzone}_active_$roll.db");
654
}
655

    
656
/* we share the log with captiveportal for now */
657
function voucher_log($priority, $message) {
658

    
659
    $message = trim($message);
660
    openlog("logportalauth", LOG_PID, LOG_LOCAL4);
661
    syslog($priority, sprintf(gettext("Voucher: %s"),$message));
662
    closelog();
663
}
664

    
665
/* Save active and used voucher DB into XML config and write it to flash
666
 * Called during reboot -> system_reboot_cleanup() and every active voucher change
667
 */
668
function voucher_save_db_to_config() {
669
    global $config, $g, $cpzone;
670

    
671
	if (is_array($config['voucher'])) {
672
		foreach ($config['voucher'] as $voucherzone => $vcfg) {
673
			$cpzone = $voucherzone;
674
			voucher_save_db_to_config_zone();
675
		}
676
	}
677
}
678

    
679
function voucher_save_db_to_config_zone() {
680
    global $config, $g, $cpzone;
681
    
682
    if (!isset($config['voucher'][$cpzone]['enable']))
683
        return;   // no vouchers or don't want to save DB's
684

    
685
    if (!is_array($config['voucher'][$cpzone]['roll']))
686
	return;
687

    
688
    $voucherlck = lock("voucher{$cpzone}", LOCK_EX);
689

    
690
    // walk all active rolls and save runtime DB's to flash
691
    $a_roll = &$config['voucher'][$cpzone]['roll'];
692
    while (list($key, $value) = each($a_roll)) {
693
        $rollent = &$a_roll[$key];
694
        $roll = $rollent['number'];
695
        $bitmask = voucher_read_used_db($roll);
696
        $rollent['used'] = base64_encode($bitmask);
697
        $active_vouchers = voucher_read_active_db($roll);
698
        $db = array();
699
		$dbi = 1;
700
        foreach($active_vouchers as $voucher => $line) {
701
            list($timestamp,$minutes) = explode(",", $line);
702
            $activent['voucher'] = $voucher;
703
            $activent['timestamp'] = $timestamp;
704
            $activent['minutes'] = $minutes;
705
            $db["v{$dbi}"] = $activent;
706
	    $dbi++;
707
        }
708
        $rollent['active'] = $db;
709
    }
710

    
711
    unlock($voucherlck);
712

    
713
    write_config("Synching vouchers");
714
    return;
715
}
716

    
717
?>
(57-57/67)