Project

General

Profile

Download (25 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
46
		$url = "http://{$syncip}";
47

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

    
56
EOF;
57

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

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

    
83
	$toreturn =  XML_RPC_Decode($resp->value());
84

    
85
	return $toreturn;
86
}
87

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

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

    
108
EOF;
109

    
110
	/* assemble xmlrpc payload */
111
	$params = array(
112
		XML_RPC_encode($password),
113
		XML_RPC_encode($execcmd)
114
	);
115

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

    
135
	$toreturn =  XML_RPC_Decode($resp->value());
136

    
137
	return $toreturn;
138
}
139

    
140
function xmlrpc_sync_used_voucher($voucher_received, $syncip, $port, $password, $username) {
141
	global $g, $config, $cpzone;
142
	require_once("xmlrpc.inc");
143
	if ($port == "443") 
144
		$url = "https://{$syncip}";
145
	else
146
		$url = "http://{$syncip}";
147

    
148
	/* Construct code that is run on remote machine */
149
	$method = 'pfsense.exec_php';
150
	$execcmd  = <<<EOF
151
	require_once('/etc/inc/voucher.inc');
152
	\$cpzone = "$cpzone";
153
	\$timeleft = voucher_auth("$voucher_received");
154
	\$toreturn = array();
155
	\$toreturn['timeleft'] = \$timeleft;
156
	\$toreturn['voucher'] = array();
157
	\$toreturn['voucher']['roll'] = \$config['voucher'][\$cpzone]['roll'];
158

    
159
EOF;
160

    
161
	/* assemble xmlrpc payload */
162
	$params = array(
163
		XML_RPC_encode($password),
164
		XML_RPC_encode($execcmd)
165
	);
166

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

    
194
	return $toreturn['timeleft'];
195
}
196

    
197
function voucher_expire($voucher_received) {
198
	global $g, $config, $cpzone;
199

    
200
	// XMLRPC Call over to the master Voucher node
201
	if(!empty($config['voucher'][$cpzone]['vouchersyncdbip'])) {
202
		$syncip   = $config['voucher'][$cpzone]['vouchersyncdbip'];
203
		$syncport = $config['voucher'][$cpzone]['vouchersyncport'];
204
		$syncpass = $config['voucher'][$cpzone]['vouchersyncpass'];
205
		$vouchersyncusername = $config['voucher'][$cpzone]['vouchersyncusername'];
206
		xmlrpc_sync_voucher_expire($voucher_received, $syncip, $syncport, $syncpass, $vouchersyncusername);
207
	}
208

    
209
	$voucherlck = lock("voucher{$cpzone}", LOCK_EX);
210

    
211
	// read rolls into assoc array with rollid as key and minutes as value
212
	$tickets_per_roll = array();
213
	$minutes_per_roll = array();
214
	if (is_array($config['voucher'][$cpzone]['roll'])) {
215
		foreach ($config['voucher'][$cpzone]['roll'] as $rollent) {
216
			$tickets_per_roll[$rollent['number']] = $rollent['count'];
217
			$minutes_per_roll[$rollent['number']] = $rollent['minutes'];
218
		}
219
	}
220

    
221
	// split into an array. Useful for multiple vouchers given
222
	$a_vouchers_received = preg_split("/[\t\n\r ]+/s", $voucher_received); 
223
	$active_dirty = false;
224
	$unsetindexes = array();
225

    
226
	// go through all received vouchers, check their valid and extract
227
	// Roll# and Ticket# using the external readvoucher binary
228
	foreach ($a_vouchers_received as $voucher) {
229
		$v = escapeshellarg($voucher);
230
		if (strlen($voucher) < 3)
231
			continue;   // seems too short to be a voucher!
232

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

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

    
270
	// Refresh active DBs
271
	if ($active_dirty == true) {
272
		foreach ($active_vouchers as $roll => $active)
273
			voucher_write_active_db($roll, $active);
274

    
275
		/* Triger a sync of the vouchers on config */
276
		send_event("service sync vouchers");
277
	}
278

    
279
	// Write back the used DB's
280
	if (is_array($bitstring)) {
281
		foreach ($bitstring as $roll => $used) {
282
			if(is_array($used)) {
283
				foreach($used as $u)
284
					voucher_write_used_db($roll, base64_encode($u));
285
			} else {
286
				voucher_write_used_db($roll, base64_encode($used));
287
			}
288
		}
289
	}
290

    
291
	unlock($voucherlck);
292

    
293
	/* Write database */
294
	if (!empty($unsetindexes))
295
		captiveportal_remove_entries($unsetindexes);
296

    
297
	return true;
298
}
299

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

    
310
	if (!isset($config['voucher'][$cpzone]['enable']))
311
		return 0;
312

    
313
	// XMLRPC Call over to the master Voucher node
314
	if(!empty($config['voucher'][$cpzone]['vouchersyncdbip'])) {
315
		$syncip   = $config['voucher'][$cpzone]['vouchersyncdbip'];
316
		$syncport = $config['voucher'][$cpzone]['vouchersyncport'];
317
		$syncpass = $config['voucher'][$cpzone]['vouchersyncpass'];
318
		$vouchersyncusername = $config['voucher'][$cpzone]['vouchersyncusername'];
319
		$remote_time_used = xmlrpc_sync_used_voucher($voucher_received, $syncip, $syncport, $syncpass, $vouchersyncusername);
320
	}
321

    
322
	$voucherlck = lock("voucher{$cpzone}", LOCK_EX);
323

    
324
	// read rolls into assoc array with rollid as key and minutes as value
325
	$tickets_per_roll = array();
326
	$minutes_per_roll = array();
327
	if (is_array($config['voucher'][$cpzone]['roll'])) {
328
		foreach ($config['voucher'][$cpzone]['roll'] as $rollent) {
329
			$tickets_per_roll[$rollent['number']] = $rollent['count'];
330
			$minutes_per_roll[$rollent['number']] = $rollent['minutes'];
331
		}
332
	}
333

    
334
	// split into an array. Useful for multiple vouchers given
335
	$a_vouchers_received = preg_split("/[\t\n\r ]+/s", $voucher_received); 
336
	$error = 0;
337
	$test_result = array();     // used to display for voucher test option in GUI
338
	$total_minutes = 0;
339
	$first_voucher = "";
340
	$first_voucher_roll = 0;
341

    
342
	// go through all received vouchers, check their valid and extract
343
	// Roll# and Ticket# using the external readvoucher binary
344
	foreach ($a_vouchers_received as $voucher) {
345
		$v = escapeshellarg($voucher);
346
		if (strlen($voucher) < 3)
347
			continue;   // seems too short to be a voucher!
348

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

    
401
	// if this was a test call, we're done. Return the result.
402
	if ($test) {
403
		if ($error) {
404
			$test_result[] = gettext("Access denied!");
405
		} else {
406
			$test_result[] = sprintf(gettext("Access granted for %d Minutes in total."),$total_minutes);
407
		}
408
		unlock($voucherlck);
409

    
410
		return $test_result;
411
	}
412

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

    
423
	// If we did a XMLRPC sync earlier check the timeleft
424
	if (!empty($config['voucher'][$cpzone]['vouchersyncdbip'])) 
425
		if($remote_time_used < $total_minutes) 
426
			$total_minutes = $remote_time_used;
427

    
428
	// All given vouchers were valid and this isn't simply a test.
429
	// Write back the used DB's
430
	if (is_array($bitstring)) {
431
		foreach ($bitstring as $roll => $used) {
432
			if(is_array($used)) {
433
				foreach($used as $u)
434
					voucher_write_used_db($roll, base64_encode($u));
435
			} else {
436
				voucher_write_used_db($roll, base64_encode($used));
437
			}
438
		}
439
	}
440

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

    
452
	$active_vouchers[$first_voucher_roll][$first_voucher] = "$timestamp,$minutes";
453
	voucher_write_active_db($first_voucher_roll, $active_vouchers[$first_voucher_roll]);
454

    
455
	/* Triger a sync of the vouchers on config */
456
	send_event("service sync vouchers");
457

    
458
	unlock($voucherlck);
459

    
460
	return $total_minutes;
461
}
462

    
463
function voucher_configure($sync = false) {
464
	global $config, $g, $cpzone;
465

    
466
	if (is_array($config['voucher'])) {
467
		foreach ($config['voucher'] as $voucherzone => $vcfg) {
468
			if ($g['booting'])
469
			    echo gettext("Enabling voucher support... ");
470
			$cpzone = $voucherzone;
471
			$error = voucher_configure_zone($sync);
472
			if ($g['booting']) {
473
				if ($error)
474
					echo "error\n";
475
				else
476
					echo "done\n";
477
			}
478
		}
479
	}
480
}
481

    
482
function voucher_configure_zone($sync = false) {
483
	global $config, $g, $cpzone;
484

    
485
	if (!isset($config['voucher'][$cpzone]['enable']))
486
		return 0;
487

    
488
	if ($sync == true)
489
	    captiveportal_syslog("Writing voucher db from sync data...");
490

    
491
	$voucherlck = lock("voucher{$cpzone}", LOCK_EX);
492

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

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

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

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

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

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

    
542
		unlock($voucherlck);
543
        }
544

    
545
	return 0;
546
}
547

    
548
/* write bitstring of used vouchers to ramdisk. 
549
 * Bitstring must already be base64_encoded!
550
 */
551
function voucher_write_used_db($roll, $vdb) {
552
	global $g, $cpzone;
553

    
554
	$fd = fopen("{$g['vardb_path']}/voucher_{$cpzone}_used_$roll.db", "w");
555
	if ($fd) {
556
		fwrite($fd, $vdb . "\n");
557
		fclose($fd);
558
	} else
559
		voucher_log(LOG_ERR, sprintf(gettext('cant write %1$s/voucher_%s_used_%2$s.db'), $g['vardb_path'], $cpzone, $roll));
560
}
561

    
562
/* return assoc array of active vouchers with activation timestamp
563
 * voucher is index. 
564
 */
565
function voucher_read_active_db($roll) {
566
	global $g, $cpzone;
567

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

    
588
				/* Triger a sync of the vouchers on config */
589
				send_event("service sync vouchers");
590
			}
591
		}
592
	}
593
	return $active;
594
}
595

    
596
/* store array of active vouchers back to DB */
597
function voucher_write_active_db($roll, $active) {
598
    global $g, $cpzone;
599

    
600
	if (!is_array($active))
601
		return;
602
    $fd = fopen("{$g['vardb_path']}/voucher_{$cpzone}_active_$roll.db", "w");
603
    if ($fd) {
604
        foreach($active as $voucher => $value)
605
            fwrite($fd, "$voucher,$value\n");
606
        fclose($fd);
607
    }
608
}
609

    
610
/* return how many vouchers are marked used on a roll */
611
function voucher_used_count($roll) {
612
    global $g, $cpzone;
613

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

    
627
function voucher_read_used_db($roll) {
628
    global $g, $cpzone;
629

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

    
644
function voucher_unlink_db($roll) {
645
    global $g, $cpzone;
646
    @unlink("{$g['vardb_path']}/voucher_{$cpzone}_used_$roll.db");
647
    @unlink("{$g['vardb_path']}/voucher_{$cpzone}_active_$roll.db");
648
}
649

    
650
/* we share the log with captiveportal for now */
651
function voucher_log($priority, $message) {
652

    
653
    $message = trim($message);
654
    openlog("logportalauth", LOG_PID, LOG_LOCAL4);
655
    syslog($priority, sprintf(gettext("Voucher: %s"),$message));
656
    closelog();
657
}
658

    
659
/* Save active and used voucher DB into XML config and write it to flash
660
 * Called during reboot -> system_reboot_cleanup() and every active voucher change
661
 */
662
function voucher_save_db_to_config() {
663
    global $config, $g, $cpzone;
664

    
665
	if (is_array($config['voucher'])) {
666
		foreach ($config['voucher'] as $voucherzone => $vcfg) {
667
			$cpzone = $voucherzone;
668
			voucher_save_db_to_config_zone();
669
		}
670
	}
671
}
672

    
673
function voucher_save_db_to_config_zone() {
674
    global $config, $g, $cpzone;
675
    
676
    if (!isset($config['voucher'][$cpzone]['enable']))
677
        return;   // no vouchers or don't want to save DB's
678

    
679
    if (!is_array($config['voucher'][$cpzone]['roll']))
680
	return;
681

    
682
    $voucherlck = lock("voucher{$cpzone}", LOCK_EX);
683

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

    
705
    unlock($voucherlck);
706

    
707
    write_config("Synching vouchers");
708
    return;
709
}
710

    
711
?>
(56-56/66)