Project

General

Profile

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