Project

General

Profile

Download (17.4 KB) Statistics
| Branch: | Tag: | Revision:
1
<?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

    
29
/*
30
	pfSense_BUILDER_BINARIES:	/usr/local/bin/voucher	/usr/local/bin/minicron
31
	pfSense_MODULE:	captiveportal
32
*/
33

    
34
/* include all configuration functions */
35

    
36
function xmlrpc_sync_used_voucher($voucher_received, $syncip, $port, $password, $username) {
37
	global $g, $config;
38
	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
EOF;
54

    
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
	$cli->setCredentials($username, $password);
65
	$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
		$resp = $cli->send($msg, "250");
74
		$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
	}
87
    return $toreturn['timeleft'];
88
}
89

    
90
/* 
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
    global $g, $config;
98

    
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
    $voucherlck = lock('voucher');
103

    
104
	// 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
		$vouchersyncusername = $a_voucher['vouchersyncusername'];
111
		$remote_time_used = xmlrpc_sync_used_voucher($voucher_received, $syncip, $syncport, $syncpass, $vouchersyncusername);
112
	}
113

    
114
    // 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
            // hmm, thats weird ... not what I expected
182
            $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
		unlock($voucherlck);
195
        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
		unlock($voucherlck);
204
        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
	// If we did a XMLRPC sync earlier check the timeleft
210
	if($a_voucher['vouchersyncdbip']) 
211
		if($remote_time_used['timeleft'] < $total_minutes) 
212
			$total_minutes = $remote_time_used['timeleft'];
213

    
214
    // All given vouchers were valid and this isn't simply a test.
215
    // Write back the used DB's
216

    
217
	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

    
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
    mark_subsystem_dirty('voucher');
245

    
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
        $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

    
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
    global $config, $g;
450
    
451
    if (!isset($config['voucher']['enable']) || $config['voucher']['saveinterval'] == 0) 
452
        return;   // no vouchers or don't want to save DB's
453

    
454
    if (!is_subsystem_dirty('voucher'))
455
        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
	$dbi = 1;
470
        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
            $db["v{$dbi}"] = $activent;
476
	    $dbi++;
477
        }
478
        $rollent['active'] = $db;
479
    }
480
    clear_subsystem_dirty('voucher');
481
    unlock($voucherlck);
482
    write_config();
483
    return;
484
}
485

    
486
?>
(44-44/54)