Project

General

Profile

Download (14.6 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[] = "$voucher ($roll/$nr) active and good for $remaining Minutes";
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[] = "$voucher ($roll/$nr) already used and expired";
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[] = "$voucher ($roll/$nr) good for {$minutes_per_roll[$roll]} Minutes";
110
                        $total_minutes += $minutes_per_roll[$roll];
111
                    }
112
                }
113
            } else {
114
                $test_result[] = "$voucher ($roll/$nr): not found on any registererd Roll";
115
            }
116
        } else {
117
            // hmm, thats weird ... not what I expected
118
            $test_result[] = "$voucher 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[] = "Access denied!";
127
        } else {
128
            $test_result[] = "Access granted for $total_minutes Minutes in total.";
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
    // All given vouchers were valid and this isn't simply a test.
146
    // Write back the used DB's
147

    
148
    if (is_array($bitstring))
149
        foreach ($bitstring as $roll => $used)
150
            voucher_write_used_db($roll, base64_encode($used));
151

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

    
157
    if ($line = $active_vouchers[$first_voucher_roll][$first_voucher]) {
158
        list($timestamp, $minutes) = explode(",", $line);
159
    } else {
160
        $timestamp = time();    // new voucher
161
        $minutes = $total_minutes;
162
    }
163

    
164
    $active_vouchers[$first_voucher_roll][$first_voucher] = "$timestamp,$minutes";
165
    voucher_write_active_db($roll, $active_vouchers[$first_voucher_roll]);
166

    
167
    // mark the DB's as dirty.
168
    mark_subsystem_dirty('voucher');
169

    
170
    unlock($voucherlck);
171

    
172
    return $total_minutes;
173
}
174

    
175
function voucher_configure() {
176
    global $config, $g;
177
    
178
    /* kill any running minicron */
179
    killbypid("{$g['varrun_path']}/vouchercron.pid");
180

    
181
    if (isset($config['voucher']['enable'])) {
182

    
183
        if ($g['booting']) {
184
            echo "Enabling voucher support... ";
185
        }
186

    
187
        // start cron if we're asked to save runtime DB periodically
188
        // to XML config if it changed
189
        $croninterval = $config['voucher']['saveinterval'] * 60; // need seconds. Config has minutes
190
        if ($croninterval) {
191
            /* start pruning process (interval defaults to 60 seconds) */
192
            mwexec("/usr/local/bin/minicron $croninterval {$g['varrun_path']}/vouchercron.pid " .
193
                    "/etc/rc.savevoucher");
194
        }
195

    
196
	$voucherlck = lock('voucher');
197
        /* write public key used to verify vouchers */
198
        $pubkey = base64_decode($config['voucher']['publickey']);
199
        $fd = fopen("{$g['varetc_path']}/voucher.public", "w");
200
        if (!$fd) {
201
            printf("Error: cannot write voucher.public\n");
202
	    unlock($voucherlck);
203
            return 1;
204
        }
205
        chmod("{$g['varetc_path']}/voucher.public", 0600);
206
        fwrite($fd, $pubkey);
207
        fclose($fd);
208

    
209
        /* write config file used by voucher binary to decode vouchers */
210
        $fd = fopen("{$g['varetc_path']}/voucher.cfg", "w");
211
        if (!$fd) {
212
            printf("Error: cannot write voucher.cfg\n");
213
	    unlock($voucherlck);
214
            return 1;
215
        }
216
        chmod("{$g['varetc_path']}/voucher.cfg", 0600);
217
        fwrite($fd, "{$config['voucher']['rollbits']},{$config['voucher']['ticketbits']},{$config['voucher']['checksumbits']},{$config['voucher']['magic']},{$config['voucher']['charset']}\n");
218
        fclose($fd);
219
	unlock($voucherlck);
220

    
221
        if ($g['booting']) {
222

    
223
            // create active and used DB per roll on ramdisk from config
224
            $a_roll = &$config['voucher']['roll'];
225
	    $voucherlck = lock('voucher');
226

    
227
            foreach ($a_roll as $rollent) {
228

    
229
                $roll = $rollent['number'];
230
                voucher_write_used_db($roll, $rollent['used']);
231
                $minutes = $rollent['minutes'];
232
                $active_vouchers = array();
233
                $a_active = &$rollent['active'];
234
                if (is_array($a_active)) {
235
                    foreach ($a_active as $activent) {
236
                        $voucher = $activent['voucher'];
237
                        $timestamp = $activent['timestamp'];
238
                        $minutes = $activent['minutes'];
239
                        // its tempting to check for expired timestamps, but during
240
                        // bootup, we most likely don't have the correct time time.
241
                        $active_vouchers[$voucher] = "$timestamp,$minutes";
242
                    }
243
                }
244
                voucher_write_active_db($roll, $active_vouchers);
245
            }
246
		
247
	    unlock($voucherlck);
248
            echo "done\n";
249
        }
250
    }
251
    return 0;
252
}
253

    
254
/* write bitstring of used vouchers to ramdisk. 
255
 * Bitstring must already be base64_encoded!
256
 */
257
function voucher_write_used_db($roll, $vdb) {
258

    
259
    global $g;
260

    
261
    $fd = fopen("{$g['vardb_path']}/voucher_used_$roll.db", "w");
262
    if ($fd) {
263
        fwrite($fd, $vdb . "\n");
264
        fclose($fd);
265
    } else {
266
        voucher_log(LOG_ERR, "cant write {$g['vardb_path']}/voucher_used_$roll.db");
267
    }
268
}
269

    
270
/* return assoc array of active vouchers with activation timestamp
271
 * voucher is index. 
272
 */
273
function voucher_read_active_db($roll) {
274

    
275
    global $g;
276

    
277
    $active = array();
278
    $dirty = 0;
279
    $file = "{$g['vardb_path']}/voucher_active_$roll.db";
280
    if (file_exists($file)) {
281
        $fd = fopen($file, "r");
282
        if ($fd) {
283
            while (!feof($fd)) {
284
                $line = trim(fgets($fd));
285
                if ($line) {
286
                    list($voucher,$timestamp,$minutes) = explode(",", $line); // voucher,timestamp
287
                    if ((($timestamp + 60*$minutes) - time()) > 0) {
288
                        $active[$voucher] = "$timestamp,$minutes";
289
                    } else {
290
                        $dirty=1;
291
                    }
292
                }
293
            }
294
            fclose($fd);
295
            if ($dirty) // if we found expired entries, lets save our snapshot
296
                voucher_write_active_db($roll, $active);
297
        }
298
    }
299
    return $active;
300
}
301

    
302
/* store array of active vouchers back to DB */
303
function voucher_write_active_db($roll, $active) {
304

    
305
    global $g;
306

    
307
    $fd = fopen("{$g['vardb_path']}/voucher_active_$roll.db", "w");
308
    if ($fd) {
309
        foreach($active as $voucher => $value)
310
            fwrite($fd, "$voucher,$value\n");
311
        fclose($fd);
312
    }
313
}
314

    
315
/* return how many vouchers are marked used on a roll */
316
function voucher_used_count($roll) {
317

    
318
    global $g;
319

    
320
    $bitstring = voucher_read_used_db($roll);
321
    $max = strlen($bitstring) * 8;
322
    $used = 0;
323
    for ($i = 1; $i <= $max; $i++) {
324
        // check if ticket already used or not. 
325
        $pos = $i >> 3;            // divide by 8 -> octet
326
        $mask = 1 << ($i % 8);  // mask to test bit in octet
327
        if (ord($bitstring[$pos]) & $mask)
328
            $used++;
329
    }   
330
    return $used;
331
}
332

    
333
function voucher_read_used_db($roll) {
334

    
335
    global $g;
336

    
337
    $vdb = "";
338
    $file = "{$g['vardb_path']}/voucher_used_$roll.db";
339
    if (file_exists($file)) {
340
        $fd = fopen($file, "r");
341
        if ($fd) {
342
            $vdb = trim(fgets($fd));
343
            fclose($fd);
344
        } else {
345
            voucher_log(LOG_ERR, "cant read {$g['vardb_path']}/voucher_used_$roll.db");
346
        }
347
    }
348
    return base64_decode($vdb);
349
}
350

    
351
function voucher_unlink_db($roll) {
352

    
353
    global $g;
354
    unlink("{$g['vardb_path']}/voucher_used_$roll.db");
355
    unlink("{$g['vardb_path']}/voucher_active_$roll.db");
356
}
357

    
358
/* we share the log with captiveportal for now */
359
function voucher_log($priority, $message) {
360

    
361
    define_syslog_variables();
362
    $message = trim($message);
363
    openlog("logportalauth", LOG_PID, LOG_LOCAL4);
364
    syslog($priority, "Voucher: " . $message);
365
    closelog();
366
}
367

    
368
/* Save active and used voucher DB into XML config and write it to flash
369
 * Called during reboot -> system_reboot_cleanup() and minicron
370
 */
371
function voucher_save_db_to_config() {
372

    
373
    global $config, $g;
374
    
375
    if (!isset($config['voucher']['enable']) || $config['voucher']['saveinterval'] == 0) 
376
        return;   // no vouchers or don't want to save DB's
377

    
378
    if (!is_subsystem_dirty('voucher'))
379
        return;     // nothing changed.
380

    
381
    $voucherlck = lock('voucher');
382

    
383
    // walk all active rolls and save runtime DB's to flash
384
    $a_roll = &$config['voucher']['roll'];
385
//    foreach ($a_roll as $rollent) {
386
    while (list($key, $value) = each($a_roll)) {
387
        $rollent = &$a_roll[$key];
388
        $roll = $rollent['number'];
389
        $bitmask = voucher_read_used_db($roll);
390
        $rollent['used'] = base64_encode($bitmask);
391
        $active_vouchers = voucher_read_active_db($roll);
392
        $db = array();
393
	$dbi = 1;
394
        foreach($active_vouchers as $voucher => $line) {
395
            list($timestamp,$minutes) = explode(",", $line);
396
            $activent['voucher'] = $voucher;
397
            $activent['timestamp'] = $timestamp;
398
            $activent['minutes'] = $minutes;
399
            $db["v{$dbi}"] = $activent;
400
	    $dbi++;
401
        }
402
        $rollent['active'] = $db;
403
    }
404
    clear_subsystem_dirty('voucher');
405
    unlock($voucherlck);
406
    write_config();
407
    return;
408
}
409

    
410
?>
(44-44/53)