Project

General

Profile

Download (17.2 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
/* 
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
    global $g, $config;
44

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

    
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
                    $test_result[] = sprintf(gettext("%1$s (%2$s/%3$s) active and good for %4$d Minutes"), $voucher, $roll, $nr, $remaining);
93
                    $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
                        $test_result[] = sprintf(gettext("%1$s (%2$s/%3$s) already used and expired"), $voucher, $roll, $nr);
104
                        $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
                        $test_result[] = sprintf(gettext("%1$s (%2$s/%3$) good for %4$d Minutes"), $voucher, $roll, $nr, $minutes_per_roll[$roll]);
110
                        $total_minutes += $minutes_per_roll[$roll];
111
                    }
112
                }
113
            } else {
114
                $test_result[] = sprintf(gettext("%1$s (%2$s/%3$s): not found on any registererd Roll"), $voucher, $roll, $nr);
115
            }
116
        } else {
117
            // hmm, thats weird ... not what I expected
118
            $test_result[] = "$voucher " . gettext("invalid:") . " $result !!";
119
            $error++;
120
        }
121
    }
122

    
123
    // if this was a test call, we're done. Return the result.
124
    if ($test) {
125
        if ($error) {
126
            $test_result[] = gettext("Access denied!");
127
        } else {
128
            $test_result[] = sprintf(gettext("Access granted for %d Minutes in total."), $total_minutes);
129
        }
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
		unlock($voucherlck);
140
        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
	// 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
    // All given vouchers were valid and this isn't simply a test.
158
    // Write back the used DB's
159

    
160
	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

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

    
189
    unlock($voucherlck);
190

    
191
    return $total_minutes;
192
}
193

    
194
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
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
            echo gettext("Enabling voucher support... ");
252
        }
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
            printf(gettext("Error: cannot write voucher.public") . "\n");
269
	    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
            printf(gettext("Error: cannot write voucher.cfg") . "\n");
280
	    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
            echo gettext("done") . "\n";
316
        }
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
        voucher_log(LOG_ERR, sprintf(gettext("cant write %1$s/voucher_used_%2$s.db"), $g['vardb_path'], $roll));
334
    }
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
        $mask = 1 << ($i % 8);  // mask to test bit in octet
394
        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
            voucher_log(LOG_ERR, sprintf(gettext("cant read %1$s/voucher_used_%2$s.db"), $g['vardb_path'], $roll));
413
        }
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
    syslog($priority, gettext("Voucher: ") . $message);
432
    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
    global $config, $g;
441
    
442
    if (!isset($config['voucher']['enable']) || $config['voucher']['saveinterval'] == 0) 
443
        return;   // no vouchers or don't want to save DB's
444

    
445
    if (!is_subsystem_dirty('voucher'))
446
        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
	$dbi = 1;
461
        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
            $db["v{$dbi}"] = $activent;
467
	    $dbi++;
468
        }
469
        $rollent['active'] = $db;
470
    }
471
    clear_subsystem_dirty('voucher');
472
    unlock($voucherlck);
473
    write_config();
474
    return;
475
}
476

    
477
?>
(44-44/54)