Project

General

Profile

Download (17.3 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php
2
/*
3
	Copyright (C) 2010 Ermal Luci <ermal.luci@gmail.com>
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	/usr/local/bin/minicron
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_used_voucher($voucher_received, $syncip, $port, $password, $username) {
41
	global $g, $config;
42
	require_once("xmlrpc.inc");
43
	if($port == "443") 
44
		$url = "https://{$syncip}:{$port}";
45
	else 
46
		$url = "http://{$syncip}:{$port}";
47

    
48
	/* Construct code that is run on remote machine */
49
	$method = 'pfsense.exec_php';
50
	$execcmd  = <<<EOF
51
	require_once('/etc/inc/voucher.inc');
52
	\$timeleft = voucher_auth($voucher_received);
53
	\$toreturn = array();
54
	\$toreturn['timeleft'] = \$timeleft;
55
	\$toreturn['voucher']['roll'] = \$config['voucher']['roll'];
56

    
57
EOF;
58

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

    
65
	log_error("Captive Portal Voucher XMLRPC sync data {$url}:{$port}.");
66
	$msg = new XML_RPC_Message($method, $params);
67
	$cli = new XML_RPC_Client('/xmlrpc.php', $url, $port);
68
	$cli->setCredentials($username, $password);
69
	$resp = $cli->send($msg, "250");
70
	if(!is_object($resp)) {
71
		$error = "A communications error occurred while attempting CaptivePortalVoucherSync XMLRPC sync with {$url}:{$port} (pfsense.exec_php).";
72
		log_error($error);
73
		file_notice("CaptivePortalVoucherSync", $error, "Communications error occurred", "");
74
		return array("timeleft" => "0");
75
	} elseif($resp->faultCode()) {
76
		$error = "An error code was received while attempting CaptivePortalVoucherSync XMLRPC sync with {$url}:{$port} - Code " . $resp->faultCode() . ": " . $resp->faultString();
77
		log_error($error);
78
		file_notice("CaptivePortalVoucherSync", $error, "Error code received", "");
79
		return array("timeleft" => "0");
80
	} else {
81
		log_error("CaptivePortalVoucherSync XMLRPC reload data success with {$url}:{$port} (pfsense.exec_php).");
82
	}
83
	$toreturn =  XML_RPC_Decode($resp->value());
84
	if(count($toreturn['voucher']['roll']) <> count($config['voucher']['roll'])) {
85
		$config['voucher']['roll'] = $toreturn['voucher']['roll'];
86
		write_config("Captive Portal Voucher database synchronized with {$url}");
87
		voucher_configure();
88
	}
89

    
90
	return $toreturn['timeleft'];
91
}
92

    
93
/* 
94
 * Authenticate a voucher and return the remaining time credit in minutes
95
 * if $test is set, don't mark the voucher as used nor add it to the list
96
 * of active vouchers
97
 * If $test is set, simply test the voucher. Don't change anything
98
 * but return a more verbose error and result message back
99
 */
100
function voucher_auth($voucher_received, $test = 0) {
101
    global $g, $config;
102

    
103
    $voucherlck = lock('voucher');
104

    
105
	// XMLRPC Call over to the master Voucher node
106
	$a_voucher = &$config['voucher'];
107
	if($a_voucher['vouchersyncdbip']) {
108
		$syncip   = $a_voucher['vouchersyncdbip'];
109
		$syncport = $a_voucher['vouchersyncport'];
110
		$syncpass = $a_voucher['vouchersyncpass'];
111
		$vouchersyncusername = $a_voucher['vouchersyncusername'];
112
		$remote_time_used = xmlrpc_sync_used_voucher($voucher_received, $syncip, $syncport, $syncpass, $vouchersyncusername);
113
	}
114

    
115
	// read rolls into assoc array with rollid as key and minutes as value
116
	$tickets_per_roll = array();
117
	$minutes_per_roll = array();
118
	if (is_array($config['voucher']['roll'])) {
119
		$a_roll = &$config['voucher']['roll'];
120
		foreach ($a_roll as $rollent) {
121
			$tickets_per_roll[$rollent['number']] = $rollent['count'];
122
			$minutes_per_roll[$rollent['number']] = $rollent['minutes'];
123
		}
124
	}
125

    
126
    // split into an array. Useful for multiple vouchers given
127
    $a_vouchers_received = split("[\t\n\r ]+",$voucher_received); 
128
    $error = 0;
129
    $test_result = array();     // used to display for voucher test option in GUI
130
    $total_minutes = 0;
131
    $first_voucher = "";
132
    $first_voucher_roll = 0;
133

    
134
    // go through all received vouchers, check their valid and extract
135
    // Roll# and Ticket# using the external readvoucher binary
136

    
137
    foreach ($a_vouchers_received as $voucher) {
138

    
139
        $v = escapeshellarg($voucher);
140
        if (strlen($voucher) < 3)
141
            continue;   // seems too short to be a voucher!
142

    
143
        $result = exec("/usr/local/bin/voucher -c {$g['varetc_path']}/voucher.cfg -k {$g['varetc_path']}/voucher.public -- $v");
144
        list($status, $roll, $nr) = explode(" ", $result);
145
        if ($status == "OK") {
146
            if (!$first_voucher) {
147
				// store first voucher. Thats the one we give the timecredit
148
                $first_voucher = $voucher;
149
                $first_voucher_roll = $roll;
150
            }
151
            // check if we have this ticket on a registered roll for this ticket 
152
            if ($tickets_per_roll[$roll] && ($nr <= $tickets_per_roll[$roll])) {
153
                // voucher is from a registered roll. 
154
                if (!isset($active_vouchers[$roll]))
155
                    $active_vouchers[$roll] = voucher_read_active_db($roll);
156
                // valid voucher. Store roll# and ticket#
157
                if ($line = $active_vouchers[$roll][$voucher]) {
158
                    list($timestamp,$minutes) = explode(",", $line);
159
                    // we have an already active voucher here.
160
                    $remaining = intval((($timestamp + 60*$minutes) - time())/60);
161
                    $test_result[] = "$voucher ($roll/$nr) active and good for $remaining Minutes";
162
                    $total_minutes += $remaining;
163
                } else {
164
                    // voucher not used. Check if ticket Id is on the roll (not too high)
165
                    // and if the ticket is marked used.
166
                    // check if voucher already marked as used
167
                    if (!isset($bitstring[$roll]))
168
                        $bitstring[$roll] = voucher_read_used_db($roll);
169
                    $pos = $nr >> 3; // divide by 8 -> octet
170
                    $mask = 1 << ($nr % 8);
171
                    if (ord($bitstring[$roll][$pos]) & $mask) {
172
                        $test_result[] = "$voucher ($roll/$nr) already used and expired";
173
						captiveportal_syslog("$voucher ($roll/$nr) already used and expired");
174
                        $total_minutes = -1;    // voucher expired
175
                        $error++;
176
                    } else {
177
                        // mark bit for this voucher as used
178
                        $bitstring[$roll][$pos] = chr(ord($bitstring[$roll][$pos]) | $mask);
179
                        $test_result[] = "$voucher ($roll/$nr) good for {$minutes_per_roll[$roll]} Minutes";
180
                        $total_minutes += $minutes_per_roll[$roll];
181
                    }
182
                }
183
            } else {
184
                $test_result[] = "$voucher ($roll/$nr): not found on any registererd Roll";
185
				captiveportal_syslog("$voucher ($roll/$nr): not found on any registererd Roll");
186
            }
187
        } else {
188
            // hmm, thats weird ... not what I expected
189
            $test_result[] = "$voucher invalid: $result !!";
190
			captiveportal_syslog("$voucher invalid: $result !!");
191
            $error++;
192
        }
193
    }
194

    
195
    // if this was a test call, we're done. Return the result.
196
    if ($test) {
197
        if ($error) {
198
            $test_result[] = "Access denied!";
199
        } else {
200
            $test_result[] = "Access granted for $total_minutes Minutes in total.";
201
        }
202
		unlock($voucherlck);
203
        return $test_result;
204
    }
205

    
206
    // if we had an error (one of the vouchers is invalid), return 0.
207
    // Discussion: we could return the time remaining for good vouchers, but then
208
    // the user wouldn't know that he used at least one invalid voucher.
209

    
210
    if ($error) {
211
		unlock($voucherlck);
212
        if ($total_minutes > 0)     // probably not needed, but want to make sure
213
            $total_minutes = 0;     // we only report -1 (expired) or 0 (no access)
214
        return $total_minutes;       // well, at least one voucher had errors. Say NO ACCESS
215
    }
216

    
217
	// If we did a XMLRPC sync earlier check the timeleft
218
	if($a_voucher['vouchersyncdbip']) 
219
		if($remote_time_used['timeleft'] < $total_minutes) 
220
			$total_minutes = $remote_time_used['timeleft'];
221

    
222
    // All given vouchers were valid and this isn't simply a test.
223
    // Write back the used DB's
224

    
225
	if (is_array($bitstring)) {
226
		foreach ($bitstring as $roll => $used) {
227
			if(is_array($used)) {
228
				foreach($used as $u)
229
					voucher_write_used_db($roll, base64_encode($u));
230
			} else {
231
				voucher_write_used_db($roll, base64_encode($used));
232
			}
233
		}
234
	}
235

    
236
    // Active DB: we only add the first voucher if multiple given
237
    // and give that one all the time credit. This allows the user to logout and
238
    // log in later using just the first voucher. It also keeps username limited
239
    // to one voucher and that voucher shows the correct time credit in 'active vouchers'
240

    
241
    if ($line = $active_vouchers[$first_voucher_roll][$first_voucher]) {
242
        list($timestamp, $minutes) = explode(",", $line);
243
    } else {
244
        $timestamp = time();    // new voucher
245
        $minutes = $total_minutes;
246
    }
247

    
248
    $active_vouchers[$first_voucher_roll][$first_voucher] = "$timestamp,$minutes";
249
    voucher_write_active_db($roll, $active_vouchers[$first_voucher_roll]);
250

    
251
    unlock($voucherlck);
252

    
253
    return $total_minutes;
254
}
255

    
256
function voucher_configure() {
257
    global $config, $g;
258

    
259
	/* kill any running minicron */
260
	killbypid("{$g['varrun_path']}/vouchercron.pid");
261

    
262
	if (!isset($config['voucher']['enable']))
263
		return 0;
264

    
265
        if ($g['booting'])
266
            echo "Enabling voucher support... ";
267

    
268
        // start cron if we're asked to save runtime DB periodically
269
        // to XML config if it changed
270
        $croninterval = $config['voucher']['saveinterval'] * 60; // need seconds. Config has minutes
271
        if ($croninterval) {
272
            /* start pruning process (interval defaults to 60 seconds) */
273
            mwexec("/usr/local/bin/minicron $croninterval {$g['varrun_path']}/vouchercron.pid " .
274
                    "/etc/rc.savevoucher");
275
        }
276

    
277
	$voucherlck = lock('voucher', LOCK_EX);
278

    
279
        /* write public key used to verify vouchers */
280
        $pubkey = base64_decode($config['voucher']['publickey']);
281
        $fd = fopen("{$g['varetc_path']}/voucher.public", "w");
282
        if (!$fd) {
283
            captiveportal_syslog("Voucher error: cannot write voucher.public\n");
284
	   		unlock($voucherlck);
285
            return 1;
286
        }
287
        fwrite($fd, $pubkey);
288
        fclose($fd);
289
        @chmod("{$g['varetc_path']}/voucher.public", 0600);
290

    
291
        /* write config file used by voucher binary to decode vouchers */
292
        $fd = fopen("{$g['varetc_path']}/voucher.cfg", "w");
293
        if (!$fd) {
294
            printf("Error: cannot write voucher.cfg\n");
295
	    unlock($voucherlck);
296
            return 1;
297
        }
298
        fwrite($fd, "{$config['voucher']['rollbits']},{$config['voucher']['ticketbits']},{$config['voucher']['checksumbits']},{$config['voucher']['magic']},{$config['voucher']['charset']}\n");
299
        fclose($fd);
300
        @chmod("{$g['varetc_path']}/voucher.cfg", 0600);
301
		unlock($voucherlck);
302

    
303
        if ($g['booting'] && is_array($config['voucher']['roll'])) {
304

    
305
            // create active and used DB per roll on ramdisk from config
306
            $a_roll = &$config['voucher']['roll'];
307
			$voucherlck = lock('voucher');
308

    
309
            foreach ($a_roll as $rollent) {
310

    
311
                $roll = $rollent['number'];
312
                voucher_write_used_db($roll, $rollent['used']);
313
                $minutes = $rollent['minutes'];
314
                $active_vouchers = array();
315
                $a_active = &$rollent['active'];
316
                if (is_array($a_active)) {
317
                    foreach ($a_active as $activent) {
318
                        $voucher = $activent['voucher'];
319
                        $timestamp = $activent['timestamp'];
320
                        $minutes = $activent['minutes'];
321
                        // its tempting to check for expired timestamps, but during
322
                        // bootup, we most likely don't have the correct time time.
323
                        $active_vouchers[$voucher] = "$timestamp,$minutes";
324
                    }
325
                }
326
                voucher_write_active_db($roll, $active_vouchers);
327
            }
328
		
329
			unlock($voucherlck);
330
            echo "done\n";
331
        }
332

    
333
	return 0;
334
}
335

    
336
/* write bitstring of used vouchers to ramdisk. 
337
 * Bitstring must already be base64_encoded!
338
 */
339
function voucher_write_used_db($roll, $vdb) {
340
	global $g;
341

    
342
	$fd = fopen("{$g['vardb_path']}/voucher_used_$roll.db", "w");
343
	if ($fd) {
344
		fwrite($fd, $vdb . "\n");
345
		fclose($fd);
346
	} else
347
		voucher_log(LOG_ERR, "cant write {$g['vardb_path']}/voucher_used_$roll.db");
348
}
349

    
350
/* return assoc array of active vouchers with activation timestamp
351
 * voucher is index. 
352
 */
353
function voucher_read_active_db($roll) {
354
	global $g;
355

    
356
	$active = array();
357
	$dirty = 0;
358
	$file = "{$g['vardb_path']}/voucher_active_$roll.db";
359
	if (file_exists($file)) {
360
		$fd = fopen($file, "r");
361
		if ($fd) {
362
			while (!feof($fd)) {
363
				$line = trim(fgets($fd));
364
				if ($line) {
365
					list($voucher,$timestamp,$minutes) = explode(",", $line); // voucher,timestamp
366
					if ((($timestamp + 60*$minutes) - time()) > 0)
367
						$active[$voucher] = "$timestamp,$minutes";
368
					else
369
						$dirty=1;
370
				}
371
			}
372
			fclose($fd);
373
			if ($dirty) // if we found expired entries, lets save our snapshot
374
				voucher_write_active_db($roll, $active);
375
		}
376
	}
377
	return $active;
378
}
379

    
380
/* store array of active vouchers back to DB */
381
function voucher_write_active_db($roll, $active) {
382
    global $g;
383

    
384
    $fd = fopen("{$g['vardb_path']}/voucher_active_$roll.db", "w");
385
    if ($fd) {
386
        foreach($active as $voucher => $value)
387
            fwrite($fd, "$voucher,$value\n");
388
        fclose($fd);
389
    }
390
}
391

    
392
/* return how many vouchers are marked used on a roll */
393
function voucher_used_count($roll) {
394
    global $g;
395

    
396
    $bitstring = voucher_read_used_db($roll);
397
    $max = strlen($bitstring) * 8;
398
    $used = 0;
399
    for ($i = 1; $i <= $max; $i++) {
400
        // check if ticket already used or not. 
401
        $pos = $i >> 3;            // divide by 8 -> octet
402
        $mask = 1 << ($i % 8);  // mask to test bit in octet
403
        if (ord($bitstring[$pos]) & $mask)
404
            $used++;
405
    }   
406
    return $used;
407
}
408

    
409
function voucher_read_used_db($roll) {
410
    global $g;
411

    
412
    $vdb = "";
413
    $file = "{$g['vardb_path']}/voucher_used_$roll.db";
414
    if (file_exists($file)) {
415
        $fd = fopen($file, "r");
416
        if ($fd) {
417
            $vdb = trim(fgets($fd));
418
            fclose($fd);
419
        } else {
420
            voucher_log(LOG_ERR, "cant read {$g['vardb_path']}/voucher_used_$roll.db");
421
        }
422
    }
423
    return base64_decode($vdb);
424
}
425

    
426
function voucher_unlink_db($roll) {
427
    global $g;
428
    @unlink("{$g['vardb_path']}/voucher_used_$roll.db");
429
    @unlink("{$g['vardb_path']}/voucher_active_$roll.db");
430
}
431

    
432
/* we share the log with captiveportal for now */
433
function voucher_log($priority, $message) {
434

    
435
    define_syslog_variables();
436
    $message = trim($message);
437
    openlog("logportalauth", LOG_PID, LOG_LOCAL4);
438
    syslog($priority, "Voucher: " . $message);
439
    closelog();
440
}
441

    
442
/* Save active and used voucher DB into XML config and write it to flash
443
 * Called during reboot -> system_reboot_cleanup() and minicron
444
 */
445
function voucher_save_db_to_config() {
446
    global $config, $g;
447
    
448
    if (!isset($config['voucher']['enable']) || $config['voucher']['saveinterval'] == 0) 
449
        return;   // no vouchers or don't want to save DB's
450

    
451
    $voucherlck = lock('voucher', LOCK_EX);
452

    
453
    // walk all active rolls and save runtime DB's to flash
454
    $a_roll = &$config['voucher']['roll'];
455
    while (list($key, $value) = each($a_roll)) {
456
        $rollent = &$a_roll[$key];
457
        $roll = $rollent['number'];
458
        $bitmask = voucher_read_used_db($roll);
459
        $rollent['used'] = base64_encode($bitmask);
460
        $active_vouchers = voucher_read_active_db($roll);
461
        $db = array();
462
		$dbi = 1;
463
        foreach($active_vouchers as $voucher => $line) {
464
            list($timestamp,$minutes) = explode(",", $line);
465
            $activent['voucher'] = $voucher;
466
            $activent['timestamp'] = $timestamp;
467
            $activent['minutes'] = $minutes;
468
            $db["v{$dbi}"] = $activent;
469
	    $dbi++;
470
        }
471
        $rollent['active'] = $db;
472
    }
473

    
474
    unlock($voucherlck);
475

    
476
    write_config();
477
    return;
478
}
479

    
480
?>
(51-51/61)