Project

General

Profile

Download (17.4 KB) Statistics
| Branch: | Tag: | Revision:
1 336e3c1c Charlie
<?php
2
/*
3
    Copyright (C) 2007 Marcel Wiget <mwiget@mac.com>.
4
    All rights reserved.
5
    
6
    Redistribution and use in source and binary forms, with or without
7
    modification, are permitted provided that the following conditions are met:
8
    
9
    1. Redistributions of source code must retain the above copyright notice,
10
       this list of conditions and the following disclaimer.
11
    
12
    2. Redistributions in binary form must reproduce the above copyright
13
       notice, this list of conditions and the following disclaimer in the
14
       documentation and/or other materials provided with the distribution.
15
    
16
    THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
17
    INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
18
    AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
19
    AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
20
    OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
21
    SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
22
    INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
23
    CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
24
    ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
25
    POSSIBILITY OF SUCH DAMAGE.
26
27
*/
28 523855b0 Scott Ullrich
29
/*
30
	pfSense_BUILDER_BINARIES:	/usr/local/bin/voucher	/usr/local/bin/minicron
31
	pfSense_MODULE:	captiveportal
32
*/
33
34 336e3c1c Charlie
/* include all configuration functions */
35
36 6c9ca678 Scott Ullrich
function xmlrpc_sync_used_voucher($voucher_received, $syncip, $port, $password, $username) {
37 fa3ab36a Scott Ullrich
	global $g, $config;
38 6c9ca678 Scott Ullrich
	require_once("xmlrpc.inc");
39
	if($port == "443") 
40
		$url = "https://{$syncip}:{$port}";
41
	else 
42
		$url = "http://{$syncip}:{$port}";
43
44
	/* Construct code that is run on remote machine */
45
	$method = 'pfsense.exec_php';
46
	$execcmd  = <<<EOF
47
	require_once('/etc/inc/voucher.inc');
48
	\$timeleft = voucher_auth($voucher_received);
49
	\$toreturn = array();
50
	\$toreturn['timeleft'] = \$timeleft;
51
	\$toreturn['voucher']['roll'] = \$config['voucher']['roll'];
52
53 fa3ab36a Scott Ullrich
EOF;
54 6c9ca678 Scott Ullrich
55
	/* assemble xmlrpc payload */
56
	$params = array(
57
		XML_RPC_encode($password),
58
		XML_RPC_encode($execcmd)
59
	);
60
61
	log_error("Captive Portal Voucher XMLRPC sync data {$url}:{$port}.");
62
	$msg = new XML_RPC_Message($method, $params);
63
	$cli = new XML_RPC_Client('/xmlrpc.php', $url, $port);
64 2f384448 Scott Ullrich
	$cli->setCredentials($username, $password);
65 6c9ca678 Scott Ullrich
	$resp = $cli->send($msg, "250");
66
	if(!$resp) {
67
		$error = "A communications error occurred while attempting CaptivePortalVoucherSync XMLRPC sync with {$url}:{$port} (pfsense.exec_php).";
68
		log_error($error);
69
		file_notice("CaptivePortalVoucherSync", $error, "Communications error occurred", "");
70
		return array("timeleft" => "0");
71
	} elseif($resp->faultCode()) {
72
		$cli->setDebug(1);
73 fa3ab36a Scott Ullrich
		$resp = $cli->send($msg, "250");
74 6c9ca678 Scott Ullrich
		$error = "An error code was received while attempting CaptivePortalVoucherSync XMLRPC sync with {$url}:{$port} - Code " . $resp->faultCode() . ": " . $resp->faultString();
75
		log_error($error);
76
		file_notice("CaptivePortalVoucherSync", $error, "Error code received", "");
77
		return array("timeleft" => "0");
78
	} else {
79
		log_error("CaptivePortalVoucherSync XMLRPC reload data success with {$url}:{$port} (pfsense.exec_php).");
80
	}
81
	$toreturn =  XML_RPC_Decode($resp->value());
82
	if(count($toreturn['voucher']['roll']) <> count($config['voucher']['roll'])) {
83
		$config['voucher']['roll'] = $toreturn['voucher']['roll'];
84
		write_config("Captive Portal Voucher database synchronized with {$url}");
85
		voucher_configure();
86 fa3ab36a Scott Ullrich
	}
87 6c9ca678 Scott Ullrich
    return $toreturn['timeleft'];
88 fa3ab36a Scott Ullrich
}
89
90 336e3c1c Charlie
/* 
91
 *Authenticate a voucher and return the remaining time credit in minutes
92
 * if $test is set, don't mark the voucher as used nor add it to the list
93
 * of active vouchers
94
 */
95
function voucher_auth($voucher_received, $test = 0) {
96
97 a8ced10b Ermal Lu?i
    global $g, $config;
98 336e3c1c Charlie
99
    // if $test is set, simply test the voucher. Don't change anything
100
    // but return a more verbose error and result message back
101
102 2ee5fe9b Ermal Lu?i
    $voucherlck = lock('voucher');
103 336e3c1c Charlie
104 6c9ca678 Scott Ullrich
	// XMLRPC Call over to the master Voucher node
105
    $a_voucher = &$config['voucher'];
106
	if($a_voucher['vouchersyncdbip']) {
107
		$syncip   = $a_voucher['vouchersyncdbip'];
108
		$syncport = $a_voucher['vouchersyncport'];
109
		$syncpass = $a_voucher['vouchersyncpass'];
110 2f384448 Scott Ullrich
		$vouchersyncusername = $a_voucher['vouchersyncusername'];
111 6c9ca678 Scott Ullrich
		$remote_time_used = xmlrpc_sync_used_voucher($voucher_received, $syncip, $syncport, $syncpass, $vouchersyncusername);
112
	}
113 fa3ab36a Scott Ullrich
114 336e3c1c Charlie
    // read rolls into assoc array with rollid as key and minutes as value
115
    $a_roll = &$config['voucher']['roll'];
116
    foreach ($a_roll as $rollent) {
117
        $tickets_per_roll[$rollent['number']] = $rollent['count'];
118
        $minutes_per_roll[$rollent['number']] = $rollent['minutes'];
119
    }
120
121
    // split into an array. Useful for multiple vouchers given
122
    $a_vouchers_received = split("[\t\n\r ]+",$voucher_received); 
123
    $error = 0;
124
    $test_result = array();     // used to display for voucher test option in GUI
125
    $total_minutes = 0;
126
    $first_voucher = "";
127
    $first_voucher_roll = 0;
128
129
    // go through all received vouchers, check their valid and extract
130
    // Roll# and Ticket# using the external readvoucher binary
131
132
    foreach ($a_vouchers_received as $voucher) {
133
134
        $v = escapeshellarg($voucher);
135
        if (strlen($voucher) < 3)
136
            continue;   // seems too short to be a voucher!
137
138
        $result = exec("/usr/local/bin/voucher -c {$g['varetc_path']}/voucher.cfg -k {$g['varetc_path']}/voucher.public -- $v");
139
        list($status, $roll, $nr) = explode(" ", $result);
140
        if ($status == "OK") {
141
            if (!$first_voucher) 
142
            {
143
                $first_voucher = $voucher;  // store first voucher. Thats the one we give the timecredit
144
                $first_voucher_roll = $roll;
145
            }
146
            // check if we have this ticket on a registered roll for this ticket 
147
            if ($tickets_per_roll[$roll] && ($nr <= $tickets_per_roll[$roll])) {
148
                // voucher is from a registered roll. 
149
                if (!isset($active_vouchers[$roll]))
150
                    $active_vouchers[$roll] = voucher_read_active_db($roll);
151
                // valid voucher. Store roll# and ticket#
152
                if ($line = $active_vouchers[$roll][$voucher]) {
153
                    list($timestamp,$minutes) = explode(",", $line);
154
                    // we have an already active voucher here.
155
                    $remaining = intval((($timestamp + 60*$minutes) - time())/60);
156
                    $test_result[] = "$voucher ($roll/$nr) active and good for $remaining Minutes";
157
                    $total_minutes += $remaining;
158
                } else {
159
                    // voucher not used. Check if ticket Id is on the roll (not too high)
160
                    // and if the ticket is marked used.
161
                    // check if voucher already marked as used
162
                    if (!isset($bitstring[$roll]))
163
                        $bitstring[$roll] = voucher_read_used_db($roll);
164
                    $pos = $nr >> 3; // divide by 8 -> octet
165
                    $mask = 1 << ($nr % 8);
166
                    if (ord($bitstring[$roll][$pos]) & $mask) {
167
                        $test_result[] = "$voucher ($roll/$nr) already used and expired";
168
                        $total_minutes = -1;    // voucher expired
169
                        $error++;
170
                    } else {
171
                        // mark bit for this voucher as used
172
                        $bitstring[$roll][$pos] = chr(ord($bitstring[$roll][$pos]) | $mask);
173
                        $test_result[] = "$voucher ($roll/$nr) good for {$minutes_per_roll[$roll]} Minutes";
174
                        $total_minutes += $minutes_per_roll[$roll];
175
                    }
176
                }
177
            } else {
178
                $test_result[] = "$voucher ($roll/$nr): not found on any registererd Roll";
179
            }
180
        } else {
181 e593f555 Chris Buechler
            // hmm, thats weird ... not what I expected
182 336e3c1c Charlie
            $test_result[] = "$voucher invalid: $result !!";
183
            $error++;
184
        }
185
    }
186
187
    // if this was a test call, we're done. Return the result.
188
    if ($test) {
189
        if ($error) {
190
            $test_result[] = "Access denied!";
191
        } else {
192
            $test_result[] = "Access granted for $total_minutes Minutes in total.";
193
        }
194 2f384448 Scott Ullrich
		unlock($voucherlck);
195 336e3c1c Charlie
        return $test_result;
196
    }
197
198
    // if we had an error (one of the vouchers is invalid), return 0.
199
    // Discussion: we could return the time remaining for good vouchers, but then
200
    // the user wouldn't know that he used at least one invalid voucher.
201
202
    if ($error) {
203 830c33be Scott Ullrich
		unlock($voucherlck);
204 336e3c1c Charlie
        if ($total_minutes > 0)     // probably not needed, but want to make sure
205
            $total_minutes = 0;     // we only report -1 (expired) or 0 (no access)
206
        return $total_minutes;       // well, at least one voucher had errors. Say NO ACCESS
207
    }
208
209 6c9ca678 Scott Ullrich
	// If we did a XMLRPC sync earlier check the timeleft
210
	if($a_voucher['vouchersyncdbip']) 
211 2f384448 Scott Ullrich
		if($remote_time_used['timeleft'] < $total_minutes) 
212 830c33be Scott Ullrich
			$total_minutes = $remote_time_used['timeleft'];
213
214 336e3c1c Charlie
    // All given vouchers were valid and this isn't simply a test.
215
    // Write back the used DB's
216
217 c14d9967 Scott Ullrich
	if (is_array($bitstring)) {
218
		foreach ($bitstring as $roll => $used) {
219
			if(is_array($used)) {
220
				foreach($used as $u)
221
					voucher_write_used_db($roll, base64_encode($u));
222
			} else {
223
				voucher_write_used_db($roll, base64_encode($used));
224
			}
225
		}
226
	}
227 336e3c1c Charlie
228
    // Active DB: we only add the first voucher if multiple given
229
    // and give that one all the time credit. This allows the user to logout and
230
    // log in later using just the first voucher. It also keeps username limited
231
    // to one voucher and that voucher shows the correct time credit in 'active vouchers'
232
233
    if ($line = $active_vouchers[$first_voucher_roll][$first_voucher]) {
234
        list($timestamp, $minutes) = explode(",", $line);
235
    } else {
236
        $timestamp = time();    // new voucher
237
        $minutes = $total_minutes;
238
    }
239
240
    $active_vouchers[$first_voucher_roll][$first_voucher] = "$timestamp,$minutes";
241
    voucher_write_active_db($roll, $active_vouchers[$first_voucher_roll]);
242
243
    // mark the DB's as dirty.
244 a8ced10b Ermal Lu?i
    mark_subsystem_dirty('voucher');
245 336e3c1c Charlie
246
    unlock($voucherlck);
247
248
    return $total_minutes;
249
}
250
251
function voucher_configure() {
252
    global $config, $g;
253
    
254
    /* kill any running minicron */
255
    killbypid("{$g['varrun_path']}/vouchercron.pid");
256
257
    if (isset($config['voucher']['enable'])) {
258
259
        if ($g['booting']) {
260
            echo "Enabling voucher support... ";
261
        }
262
263
        // start cron if we're asked to save runtime DB periodically
264
        // to XML config if it changed
265
        $croninterval = $config['voucher']['saveinterval'] * 60; // need seconds. Config has minutes
266
        if ($croninterval) {
267
            /* start pruning process (interval defaults to 60 seconds) */
268
            mwexec("/usr/local/bin/minicron $croninterval {$g['varrun_path']}/vouchercron.pid " .
269
                    "/etc/rc.savevoucher");
270
        }
271
272
	$voucherlck = lock('voucher');
273
        /* write public key used to verify vouchers */
274
        $pubkey = base64_decode($config['voucher']['publickey']);
275
        $fd = fopen("{$g['varetc_path']}/voucher.public", "w");
276
        if (!$fd) {
277
            printf("Error: cannot write voucher.public\n");
278
	    unlock($voucherlck);
279
            return 1;
280
        }
281
        chmod("{$g['varetc_path']}/voucher.public", 0600);
282
        fwrite($fd, $pubkey);
283
        fclose($fd);
284
285
        /* write config file used by voucher binary to decode vouchers */
286
        $fd = fopen("{$g['varetc_path']}/voucher.cfg", "w");
287
        if (!$fd) {
288
            printf("Error: cannot write voucher.cfg\n");
289
	    unlock($voucherlck);
290
            return 1;
291
        }
292
        chmod("{$g['varetc_path']}/voucher.cfg", 0600);
293
        fwrite($fd, "{$config['voucher']['rollbits']},{$config['voucher']['ticketbits']},{$config['voucher']['checksumbits']},{$config['voucher']['magic']},{$config['voucher']['charset']}\n");
294
        fclose($fd);
295
	unlock($voucherlck);
296
297
        if ($g['booting']) {
298
299
            // create active and used DB per roll on ramdisk from config
300
            $a_roll = &$config['voucher']['roll'];
301
	    $voucherlck = lock('voucher');
302
303
            foreach ($a_roll as $rollent) {
304
305
                $roll = $rollent['number'];
306
                voucher_write_used_db($roll, $rollent['used']);
307
                $minutes = $rollent['minutes'];
308
                $active_vouchers = array();
309
                $a_active = &$rollent['active'];
310
                if (is_array($a_active)) {
311
                    foreach ($a_active as $activent) {
312
                        $voucher = $activent['voucher'];
313
                        $timestamp = $activent['timestamp'];
314
                        $minutes = $activent['minutes'];
315
                        // its tempting to check for expired timestamps, but during
316
                        // bootup, we most likely don't have the correct time time.
317
                        $active_vouchers[$voucher] = "$timestamp,$minutes";
318
                    }
319
                }
320
                voucher_write_active_db($roll, $active_vouchers);
321
            }
322
		
323
	    unlock($voucherlck);
324
            echo "done\n";
325
        }
326
    }
327
    return 0;
328
}
329
330
/* write bitstring of used vouchers to ramdisk. 
331
 * Bitstring must already be base64_encoded!
332
 */
333
function voucher_write_used_db($roll, $vdb) {
334
335
    global $g;
336
337
    $fd = fopen("{$g['vardb_path']}/voucher_used_$roll.db", "w");
338
    if ($fd) {
339
        fwrite($fd, $vdb . "\n");
340
        fclose($fd);
341
    } else {
342
        voucher_log(LOG_ERR, "cant write {$g['vardb_path']}/voucher_used_$roll.db");
343
    }
344
}
345
346
/* return assoc array of active vouchers with activation timestamp
347
 * voucher is index. 
348
 */
349
function voucher_read_active_db($roll) {
350
351
    global $g;
352
353
    $active = array();
354
    $dirty = 0;
355
    $file = "{$g['vardb_path']}/voucher_active_$roll.db";
356
    if (file_exists($file)) {
357
        $fd = fopen($file, "r");
358
        if ($fd) {
359
            while (!feof($fd)) {
360
                $line = trim(fgets($fd));
361
                if ($line) {
362
                    list($voucher,$timestamp,$minutes) = explode(",", $line); // voucher,timestamp
363
                    if ((($timestamp + 60*$minutes) - time()) > 0) {
364
                        $active[$voucher] = "$timestamp,$minutes";
365
                    } else {
366
                        $dirty=1;
367
                    }
368
                }
369
            }
370
            fclose($fd);
371
            if ($dirty) // if we found expired entries, lets save our snapshot
372
                voucher_write_active_db($roll, $active);
373
        }
374
    }
375
    return $active;
376
}
377
378
/* store array of active vouchers back to DB */
379
function voucher_write_active_db($roll, $active) {
380
381
    global $g;
382
383
    $fd = fopen("{$g['vardb_path']}/voucher_active_$roll.db", "w");
384
    if ($fd) {
385
        foreach($active as $voucher => $value)
386
            fwrite($fd, "$voucher,$value\n");
387
        fclose($fd);
388
    }
389
}
390
391
/* return how many vouchers are marked used on a roll */
392
function voucher_used_count($roll) {
393
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 7ec341cb Ermal Lu?i
        $mask = 1 << ($i % 8);  // mask to test bit in octet
403 336e3c1c Charlie
        if (ord($bitstring[$pos]) & $mask)
404
            $used++;
405
    }   
406
    return $used;
407
}
408
409
function voucher_read_used_db($roll) {
410
411
    global $g;
412
413
    $vdb = "";
414
    $file = "{$g['vardb_path']}/voucher_used_$roll.db";
415
    if (file_exists($file)) {
416
        $fd = fopen($file, "r");
417
        if ($fd) {
418
            $vdb = trim(fgets($fd));
419
            fclose($fd);
420
        } else {
421
            voucher_log(LOG_ERR, "cant read {$g['vardb_path']}/voucher_used_$roll.db");
422
        }
423
    }
424
    return base64_decode($vdb);
425
}
426
427
function voucher_unlink_db($roll) {
428
429
    global $g;
430
    unlink("{$g['vardb_path']}/voucher_used_$roll.db");
431
    unlink("{$g['vardb_path']}/voucher_active_$roll.db");
432
}
433
434
/* we share the log with captiveportal for now */
435
function voucher_log($priority, $message) {
436
437
    define_syslog_variables();
438
    $message = trim($message);
439
    openlog("logportalauth", LOG_PID, LOG_LOCAL4);
440
    syslog($priority, "Voucher: " . $message);
441
    closelog();
442
}
443
444
/* Save active and used voucher DB into XML config and write it to flash
445
 * Called during reboot -> system_reboot_cleanup() and minicron
446
 */
447
function voucher_save_db_to_config() {
448
449 a8ced10b Ermal Lu?i
    global $config, $g;
450 336e3c1c Charlie
    
451
    if (!isset($config['voucher']['enable']) || $config['voucher']['saveinterval'] == 0) 
452
        return;   // no vouchers or don't want to save DB's
453
454 a8ced10b Ermal Lu?i
    if (!is_subsystem_dirty('voucher'))
455 336e3c1c Charlie
        return;     // nothing changed.
456
457
    $voucherlck = lock('voucher');
458
459
    // walk all active rolls and save runtime DB's to flash
460
    $a_roll = &$config['voucher']['roll'];
461
//    foreach ($a_roll as $rollent) {
462
    while (list($key, $value) = each($a_roll)) {
463
        $rollent = &$a_roll[$key];
464
        $roll = $rollent['number'];
465
        $bitmask = voucher_read_used_db($roll);
466
        $rollent['used'] = base64_encode($bitmask);
467
        $active_vouchers = voucher_read_active_db($roll);
468
        $db = array();
469 451ec561 Ermal Lu?i
	$dbi = 1;
470 336e3c1c Charlie
        foreach($active_vouchers as $voucher => $line) {
471
            list($timestamp,$minutes) = explode(",", $line);
472
            $activent['voucher'] = $voucher;
473
            $activent['timestamp'] = $timestamp;
474
            $activent['minutes'] = $minutes;
475 451ec561 Ermal Lu?i
            $db["v{$dbi}"] = $activent;
476
	    $dbi++;
477 336e3c1c Charlie
        }
478
        $rollent['active'] = $db;
479
    }
480 a8ced10b Ermal Lu?i
    clear_subsystem_dirty('voucher');
481 156487ed Ermal Lu?i
    unlock($voucherlck);
482 336e3c1c Charlie
    write_config();
483
    return;
484
}
485
486 25a2bf77 Scott Ullrich
?>