Project

General

Profile

Download (25.8 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
	require_once('/etc/inc/captiveportal.inc');
57
	require_once('/etc/inc/voucher.inc');
58
	\$cpzone = "$cpzone";
59
	voucher_expire("$vouchers");
60

    
61
EOF;
62

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

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

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

    
90
	return $toreturn;
91
}
92

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

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

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

    
118
EOF;
119

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

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

    
145
	$toreturn =  XML_RPC_Decode($resp->value());
146

    
147
	return $toreturn;
148
}
149

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

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

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

    
174
EOF;
175

    
176
	/* assemble xmlrpc payload */
177
	$params = array(
178
		XML_RPC_encode($password),
179
		XML_RPC_encode($execcmd)
180
	);
181

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

    
209
	return $toreturn['timeleft'];
210
}
211

    
212
function voucher_expire($voucher_received) {
213
	global $g, $config, $cpzone;
214

    
215
	// XMLRPC Call over to the master Voucher node
216
	if(!empty($config['voucher'][$cpzone]['vouchersyncdbip'])) {
217
		$syncip   = $config['voucher'][$cpzone]['vouchersyncdbip'];
218
		$syncport = $config['voucher'][$cpzone]['vouchersyncport'];
219
		$syncpass = $config['voucher'][$cpzone]['vouchersyncpass'];
220
		$vouchersyncusername = $config['voucher'][$cpzone]['vouchersyncusername'];
221
		xmlrpc_sync_voucher_expire($voucher_received, $syncip, $syncport, $syncpass, $vouchersyncusername);
222
	}
223

    
224
	$voucherlck = lock("voucher{$cpzone}", LOCK_EX);
225

    
226
	// read rolls into assoc array with rollid as key and minutes as value
227
	$tickets_per_roll = array();
228
	$minutes_per_roll = array();
229
	if (is_array($config['voucher'][$cpzone]['roll'])) {
230
		foreach ($config['voucher'][$cpzone]['roll'] as $rollent) {
231
			$tickets_per_roll[$rollent['number']] = $rollent['count'];
232
			$minutes_per_roll[$rollent['number']] = $rollent['minutes'];
233
		}
234
	}
235

    
236
	// split into an array. Useful for multiple vouchers given
237
	$a_vouchers_received = preg_split("/[\t\n\r ]+/s", $voucher_received); 
238
	$active_dirty = false;
239
	$unsetindexes = array();
240

    
241
	// go through all received vouchers, check their valid and extract
242
	// Roll# and Ticket# using the external readvoucher binary
243
	foreach ($a_vouchers_received as $voucher) {
244
		$v = escapeshellarg($voucher);
245
		if (strlen($voucher) < 3)
246
			continue;   // seems too short to be a voucher!
247

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

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

    
285
	// Refresh active DBs
286
	if ($active_dirty == true) {
287
		foreach ($active_vouchers as $roll => $active)
288
			voucher_write_active_db($roll, $active);
289

    
290
		/* Triger a sync of the vouchers on config */
291
		send_event("service sync vouchers");
292
	}
293

    
294
	// Write back the used DB's
295
	if (is_array($bitstring)) {
296
		foreach ($bitstring as $roll => $used) {
297
			if(is_array($used)) {
298
				foreach($used as $u)
299
					voucher_write_used_db($roll, base64_encode($u));
300
			} else {
301
				voucher_write_used_db($roll, base64_encode($used));
302
			}
303
		}
304
	}
305

    
306
	unlock($voucherlck);
307

    
308
	/* Write database */
309
	if (!empty($unsetindexes))
310
		captiveportal_remove_entries($unsetindexes);
311

    
312
	return true;
313
}
314

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

    
325
	if (!isset($config['voucher'][$cpzone]['enable']))
326
		return 0;
327

    
328
	// XMLRPC Call over to the master Voucher node
329
	if(!empty($config['voucher'][$cpzone]['vouchersyncdbip'])) {
330
		$syncip   = $config['voucher'][$cpzone]['vouchersyncdbip'];
331
		$syncport = $config['voucher'][$cpzone]['vouchersyncport'];
332
		$syncpass = $config['voucher'][$cpzone]['vouchersyncpass'];
333
		$vouchersyncusername = $config['voucher'][$cpzone]['vouchersyncusername'];
334
		$remote_time_used = xmlrpc_sync_used_voucher($voucher_received, $syncip, $syncport, $syncpass, $vouchersyncusername);
335
	}
336

    
337
	$voucherlck = lock("voucher{$cpzone}", LOCK_EX);
338

    
339
	// read rolls into assoc array with rollid as key and minutes as value
340
	$tickets_per_roll = array();
341
	$minutes_per_roll = array();
342
	if (is_array($config['voucher'][$cpzone]['roll'])) {
343
		foreach ($config['voucher'][$cpzone]['roll'] as $rollent) {
344
			$tickets_per_roll[$rollent['number']] = $rollent['count'];
345
			$minutes_per_roll[$rollent['number']] = $rollent['minutes'];
346
		}
347
	}
348

    
349
	// split into an array. Useful for multiple vouchers given
350
	$a_vouchers_received = preg_split("/[\t\n\r ]+/s", $voucher_received); 
351
	$error = 0;
352
	$test_result = array();     // used to display for voucher test option in GUI
353
	$total_minutes = 0;
354
	$first_voucher = "";
355
	$first_voucher_roll = 0;
356

    
357
	// go through all received vouchers, check their valid and extract
358
	// Roll# and Ticket# using the external readvoucher binary
359
	foreach ($a_vouchers_received as $voucher) {
360
		$v = escapeshellarg($voucher);
361
		if (strlen($voucher) < 3)
362
			continue;   // seems too short to be a voucher!
363

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

    
416
	// if this was a test call, we're done. Return the result.
417
	if ($test) {
418
		if ($error) {
419
			$test_result[] = gettext("Access denied!");
420
		} else {
421
			$test_result[] = sprintf(gettext("Access granted for %d Minutes in total."),$total_minutes);
422
		}
423
		unlock($voucherlck);
424

    
425
		return $test_result;
426
	}
427

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

    
438
	// If we did a XMLRPC sync earlier check the timeleft
439
	if (!empty($config['voucher'][$cpzone]['vouchersyncdbip'])) 
440
		if($remote_time_used < $total_minutes) 
441
			$total_minutes = $remote_time_used;
442

    
443
	// All given vouchers were valid and this isn't simply a test.
444
	// Write back the used DB's
445
	if (is_array($bitstring)) {
446
		foreach ($bitstring as $roll => $used) {
447
			if(is_array($used)) {
448
				foreach($used as $u)
449
					voucher_write_used_db($roll, base64_encode($u));
450
			} else {
451
				voucher_write_used_db($roll, base64_encode($used));
452
			}
453
		}
454
	}
455

    
456
	// Active DB: we only add the first voucher if multiple given
457
	// and give that one all the time credit. This allows the user to logout and
458
	// log in later using just the first voucher. It also keeps username limited
459
	// to one voucher and that voucher shows the correct time credit in 'active vouchers'
460
	if (!empty($active_vouchers[$first_voucher_roll][$first_voucher])) {
461
		list($timestamp, $minutes) = explode(",", $active_vouchers[$first_voucher_roll][$first_voucher]);
462
	} else {
463
		$timestamp = time();    // new voucher
464
		$minutes = $total_minutes;
465
	}
466

    
467
	$active_vouchers[$first_voucher_roll][$first_voucher] = "$timestamp,$minutes";
468
	voucher_write_active_db($first_voucher_roll, $active_vouchers[$first_voucher_roll]);
469

    
470
	/* Triger a sync of the vouchers on config */
471
	send_event("service sync vouchers");
472

    
473
	unlock($voucherlck);
474

    
475
	return $total_minutes;
476
}
477

    
478
function voucher_configure($sync = false) {
479
	global $config, $g, $cpzone;
480

    
481
	if (is_array($config['voucher'])) {
482
		foreach ($config['voucher'] as $voucherzone => $vcfg) {
483
			if ($g['booting'])
484
			    echo gettext("Enabling voucher support... ");
485
			$cpzone = $voucherzone;
486
			$error = voucher_configure_zone($sync);
487
			if ($g['booting']) {
488
				if ($error)
489
					echo "error\n";
490
				else
491
					echo "done\n";
492
			}
493
		}
494
	}
495
}
496

    
497
function voucher_configure_zone($sync = false) {
498
	global $config, $g, $cpzone;
499

    
500
	if (!isset($config['voucher'][$cpzone]['enable']))
501
		return 0;
502

    
503
	if ($sync == true)
504
	    captiveportal_syslog("Writing voucher db from sync data...");
505

    
506
	$voucherlck = lock("voucher{$cpzone}", LOCK_EX);
507

    
508
        /* write public key used to verify vouchers */
509
        $pubkey = base64_decode($config['voucher'][$cpzone]['publickey']);
510
        $fd = fopen("{$g['varetc_path']}/voucher_{$cpzone}.public", "w");
511
        if (!$fd) {
512
            captiveportal_syslog("Voucher error: cannot write voucher.public\n");
513
	    unlock($voucherlck);
514
            return 1;
515
        }
516
        fwrite($fd, $pubkey);
517
        fclose($fd);
518
        @chmod("{$g['varetc_path']}/voucher_{$cpzone}.public", 0600);
519

    
520
        /* write config file used by voucher binary to decode vouchers */
521
        $fd = fopen("{$g['varetc_path']}/voucher_{$cpzone}.cfg", "w");
522
        if (!$fd) {
523
	    printf(gettext("Error: cannot write voucher.cfg") . "\n");
524
	    unlock($voucherlck);
525
            return 1;
526
        }
527
        fwrite($fd, "{$config['voucher'][$cpzone]['rollbits']},{$config['voucher'][$cpzone]['ticketbits']},{$config['voucher'][$cpzone]['checksumbits']},{$config['voucher'][$cpzone]['magic']},{$config['voucher'][$cpzone]['charset']}\n");
528
        fclose($fd);
529
        @chmod("{$g['varetc_path']}/voucher_{$cpzone}.cfg", 0600);
530
	unlock($voucherlck);
531

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

    
534
		$voucherlck = lock("voucher{$cpzone}", LOCK_EX);
535

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

    
539
                $roll = $rollent['number'];
540
                voucher_write_used_db($roll, $rollent['used']);
541
                $minutes = $rollent['minutes'];
542
                $active_vouchers = array();
543
                $a_active = &$rollent['active'];
544
                if (is_array($a_active)) {
545
                    foreach ($a_active as $activent) {
546
                        $voucher = $activent['voucher'];
547
                        $timestamp = $activent['timestamp'];
548
                        $minutes = $activent['minutes'];
549
                        // its tempting to check for expired timestamps, but during
550
                        // bootup, we most likely don't have the correct time time.
551
                        $active_vouchers[$voucher] = "$timestamp,$minutes";
552
                    }
553
                }
554
                voucher_write_active_db($roll, $active_vouchers);
555
            }
556

    
557
		unlock($voucherlck);
558
        }
559

    
560
	return 0;
561
}
562

    
563
/* write bitstring of used vouchers to ramdisk. 
564
 * Bitstring must already be base64_encoded!
565
 */
566
function voucher_write_used_db($roll, $vdb) {
567
	global $g, $cpzone;
568

    
569
	$fd = fopen("{$g['vardb_path']}/voucher_{$cpzone}_used_$roll.db", "w");
570
	if ($fd) {
571
		fwrite($fd, $vdb . "\n");
572
		fclose($fd);
573
	} else
574
		voucher_log(LOG_ERR, sprintf(gettext('cant write %1$s/voucher_%s_used_%2$s.db'), $g['vardb_path'], $cpzone, $roll));
575
}
576

    
577
/* return assoc array of active vouchers with activation timestamp
578
 * voucher is index. 
579
 */
580
function voucher_read_active_db($roll) {
581
	global $g, $cpzone;
582

    
583
	$active = array();
584
	$dirty = 0;
585
	$file = "{$g['vardb_path']}/voucher_{$cpzone}_active_$roll.db";
586
	if (file_exists($file)) {
587
		$fd = fopen($file, "r");
588
		if ($fd) {
589
			while (!feof($fd)) {
590
				$line = trim(fgets($fd));
591
				if ($line) {
592
					list($voucher,$timestamp,$minutes) = explode(",", $line); // voucher,timestamp
593
					if ((($timestamp + (60*$minutes)) - time()) > 0)
594
						$active[$voucher] = "$timestamp,$minutes";
595
					else
596
						$dirty=1;
597
				}
598
			}
599
			fclose($fd);
600
			if ($dirty) { // if we found expired entries, lets save our snapshot
601
				voucher_write_active_db($roll, $active);
602

    
603
				/* Triger a sync of the vouchers on config */
604
				send_event("service sync vouchers");
605
			}
606
		}
607
	}
608
	return $active;
609
}
610

    
611
/* store array of active vouchers back to DB */
612
function voucher_write_active_db($roll, $active) {
613
    global $g, $cpzone;
614

    
615
	if (!is_array($active))
616
		return;
617
    $fd = fopen("{$g['vardb_path']}/voucher_{$cpzone}_active_$roll.db", "w");
618
    if ($fd) {
619
        foreach($active as $voucher => $value)
620
            fwrite($fd, "$voucher,$value\n");
621
        fclose($fd);
622
    }
623
}
624

    
625
/* return how many vouchers are marked used on a roll */
626
function voucher_used_count($roll) {
627
    global $g, $cpzone;
628

    
629
    $bitstring = voucher_read_used_db($roll);
630
    $max = strlen($bitstring) * 8;
631
    $used = 0;
632
    for ($i = 1; $i <= $max; $i++) {
633
        // check if ticket already used or not. 
634
        $pos = $i >> 3;            // divide by 8 -> octet
635
        $mask = 1 << ($i % 8);  // mask to test bit in octet
636
        if (ord($bitstring[$pos]) & $mask)
637
            $used++;
638
    }   
639
    return $used;
640
}
641

    
642
function voucher_read_used_db($roll) {
643
    global $g, $cpzone;
644

    
645
    $vdb = "";
646
    $file = "{$g['vardb_path']}/voucher_{$cpzone}_used_$roll.db";
647
    if (file_exists($file)) {
648
        $fd = fopen($file, "r");
649
        if ($fd) {
650
            $vdb = trim(fgets($fd));
651
            fclose($fd);
652
        } else {
653
	    voucher_log(LOG_ERR, sprintf(gettext('cant read %1$s/voucher_%s_used_%2$s.db'), $g['vardb_path'], $cpzone, $roll));
654
        }
655
    }
656
    return base64_decode($vdb);
657
}
658

    
659
function voucher_unlink_db($roll) {
660
    global $g, $cpzone;
661
    @unlink("{$g['vardb_path']}/voucher_{$cpzone}_used_$roll.db");
662
    @unlink("{$g['vardb_path']}/voucher_{$cpzone}_active_$roll.db");
663
}
664

    
665
/* we share the log with captiveportal for now */
666
function voucher_log($priority, $message) {
667

    
668
    $message = trim($message);
669
    openlog("logportalauth", LOG_PID, LOG_LOCAL4);
670
    syslog($priority, sprintf(gettext("Voucher: %s"),$message));
671
    closelog();
672
}
673

    
674
/* Save active and used voucher DB into XML config and write it to flash
675
 * Called during reboot -> system_reboot_cleanup() and every active voucher change
676
 */
677
function voucher_save_db_to_config() {
678
    global $config, $g, $cpzone;
679

    
680
	if (is_array($config['voucher'])) {
681
		foreach ($config['voucher'] as $voucherzone => $vcfg) {
682
			$cpzone = $voucherzone;
683
			voucher_save_db_to_config_zone();
684
		}
685
	}
686
}
687

    
688
function voucher_save_db_to_config_zone() {
689
    global $config, $g, $cpzone;
690
    
691
    if (!isset($config['voucher'][$cpzone]['enable']))
692
        return;   // no vouchers or don't want to save DB's
693

    
694
    if (!is_array($config['voucher'][$cpzone]['roll']))
695
	return;
696

    
697
    $voucherlck = lock("voucher{$cpzone}", LOCK_EX);
698

    
699
    // walk all active rolls and save runtime DB's to flash
700
    $a_roll = &$config['voucher'][$cpzone]['roll'];
701
    while (list($key, $value) = each($a_roll)) {
702
        $rollent = &$a_roll[$key];
703
        $roll = $rollent['number'];
704
        $bitmask = voucher_read_used_db($roll);
705
        $rollent['used'] = base64_encode($bitmask);
706
        $active_vouchers = voucher_read_active_db($roll);
707
        $db = array();
708
		$dbi = 1;
709
        foreach($active_vouchers as $voucher => $line) {
710
            list($timestamp,$minutes) = explode(",", $line);
711
            $activent['voucher'] = $voucher;
712
            $activent['timestamp'] = $timestamp;
713
            $activent['minutes'] = $minutes;
714
            $db["v{$dbi}"] = $activent;
715
	    $dbi++;
716
        }
717
        $rollent['active'] = $db;
718
    }
719

    
720
    unlock($voucherlck);
721

    
722
    write_config("Synching vouchers");
723
    return;
724
}
725

    
726
?>
(56-56/66)