sync
[Pman.Admin] / Iptables.php
1 <?php
2 /***
3  * how this might work..
4  *
5  * a) login - if it's a new IP not seen that day
6  * --> touch /tmp/run_pman_admin_iptables
7  *
8  * cron every minute... ?? << could do some kind of IPC?!?
9  *
10  * if file exists -> run this code.
11  *
12  * This code finds all the IP's used in the last 24 hours.
13  * and opens the firew all for them.
14  *
15  *
16  *
17  *
18  *
19  
20  
21  *
22  *
23  */
24
25 require_once 'Pman.php';
26
27 class Pman_Admin_Iptables extends Pman {
28     
29     static $cli_desc = "Read ip addresses that have been used to log in, and add them to the iptables list..";
30     
31    
32     
33     function getAuth()
34     {
35         // add locking here..
36         
37         if (!$this->bootLoader->cli) {
38             die("cli only");
39         }
40     }
41     
42     function monitorFile()
43     {
44         $ev = DB_DataObject::Factory('Events');
45         $db = $ev->database();
46         
47         return '/tmp/run_pman_admin_iptables-'.$db;
48     }
49     
50     function get($opt = '', $opts = Array())
51     {
52         
53         // monitor file
54         
55         $mf = $this->monitorFile();
56         
57         $fe = file_exists($mf);
58         if (empty($opt)) {
59             if (!$fe) {
60                 exit;
61             }
62         }
63         if ($fe) {
64             unlink($mf);
65         }
66         
67         // find IP's that have been used to log in.
68         // dump them to the iptables file.
69         // if it's different - apply it...
70         //DB_DataObject::debugLevel(1);
71         // need to get a list of users who have Admin.Iptables rights..
72         /*$gr = DB_DataObject::factory('group_rights');
73         $grps = $gr->groupsWithRights('Admin.Iptables', 'S');
74         
75         $gr = DB_DataObject::factory('groups');
76         $gr->get('name', 'Administrators');
77         $grps[] = $gr->id;
78         
79         $gm = DB_DataObject::factory('group_members');
80         $gm->whereAddIn('group_id', $grps, 'int');
81         $gm->selectAdd();
82         $gm->selectAdd('distinct(user_id) as user_id');
83         $peps = $gm->fetchAll('user_id');
84         
85         
86         */
87         //DB_DataObject::debugLevel(1);
88         
89         $p = DB_DataObject::Factory('core_person');
90         $p->autoJoin();
91         $p->whereAdd("join_company_id_id.comptype = 'OWNER'");
92         $p->selectAdd();
93         $p->selectAdd("{$p->tableName()}.id as  id");
94         
95         $peps = $p->fetchAll('id');
96         
97         
98         switch( $p->getDatabaseConnection()->phptype) {
99             case 'mysql':
100                 $interval = "INTERVAL 1 DAY";
101                 break;
102             case 'pgsql':
103                 $interval = " INTERVAL '1 DAY'";
104                 break;
105             default:
106                 die("database NOT SUPPORTED?!");
107         }
108         
109         $e = DB_DataObject::factory('Events');
110         $e->action = 'LOGIN';
111         $e->selectAdd();
112         $e->selectAdd(" 
113             distinct(ipaddr) as ipaddr,
114             max(event_when) + $interval as expires
115                      
116         ");
117         $e->person_table = DB_DataObject::factory('core_person')->tableName();
118         $e->whereAddIn('person_id', $peps, 'int');
119         $e->groupBy('ipaddr');
120         $e->whereAdd("event_when > NOW() - $interval");
121                 
122        
123         $ips = $e->fetchAll('ipaddr','expires');
124
125         require_once 'System.php';
126         //inet addr:202.67.151.28  Bcast:202.67.151.255  Mask:255.255.255.0
127         $ifconfig = System::which('ifconfig','/sbin/ifconfig');
128         
129         if (!$ifconfig || !file_exists($ifconfig)) {
130             $this->jerr("ifconfig could not be found.");
131         }
132
133         $if = $this->exec($ifconfig);
134         
135          
136         foreach(explode("\n", $if) as $l) {
137             //var_dump($l);
138             if (!preg_match('/inet addr/', $l)) {
139                 continue;
140             }
141             $match = array();
142             preg_match('/\s*inet addr:([0-9.]+)\s+/', $l, $match);
143             $ips[$match[1]] = ''; // no expiry...
144             
145         }
146         $this->ips = $ips;
147         $cache = ini_get('session.save-path') . '/pman_admin_iptables.cache';
148         
149         $this->updateTables();
150         
151          
152         exit;
153
154     }
155     
156     function readChain($chain)
157     {
158         
159         static $iptables;
160         
161         if (!$iptables) {
162             require_once 'System.php';
163             
164             $iptables = System::which('iptables', '/sbin/iptables');
165             
166             if (!$iptables || !file_exists($iptables)) {
167                 $this->jerr("iptables could not be found.");
168             }
169         }
170         // this should have been set up already..
171         // in the base firewall code.
172        
173         $res = $this->exec("{$iptables} -L {$chain} -v -n --line-numbers");   
174         
175         
176         
177         $lastrulenum = 1;
178        
179         $remove = array();
180         $cur = array();
181         $head = false;
182         
183         foreach(explode("\n", $res) as $i => $line) {
184             if ($i == 1) {
185                 $head = preg_split('/\s+/', $line);
186                 $head[10] = 'comments';
187             }
188             if ($i < 2) {
189                 continue;
190             }
191             
192             $ar = preg_split('/\s+/', $line);
193             if (count($ar) < 3) {
194                 continue;
195             }
196             $ar[10] = implode(' ',array_slice($ar, 10));
197             $row = array();
198             foreach($head as $k=>$v) {
199                 $row[$v] = $ar[$k];
200             }
201            // print_r($row);
202             //var_dump($row['target']);
203              
204             
205             // got input rules now..
206             if (!empty($row['comments'])) {
207                 
208                 $row['comments'] = preg_replace('#^/\*#', '', trim($row['comments']) );
209                 $row['comments'] = preg_replace('#\*/$#', '', $row['comments'] );
210                 foreach((array)json_decode($row['comments']) as $k=>$v) {
211                     $row[$k] = $v;
212                 }
213             }
214             $rows[] = $row;
215                           
216              
217         }
218         if (empty($head)) {
219             return false;
220         }
221         
222         return  $rows;
223     
224     }
225     
226     
227     
228     function updateTables()
229     {
230         static $iptables;
231         
232         if (!$iptables) {
233             require_once 'System.php';
234             
235             $iptables = System::which('iptables', '/sbin/iptables');
236             
237             if (!$iptables || !file_exists($iptables)) {
238                 $this->jerr("iptables could not be found.");
239             }
240         }
241         // this should have been set up already..
242         // in the base firewall code.
243        
244        
245         $rows = $this->readChain('INPUT');
246         $gotpg = false;
247         foreach($rows as $r) {
248             if ($r['target'] == 'postgres') {
249                 $gotpg = true;
250             }
251         }
252         if (!$gotpg) {
253             $this->exec("{$iptables} -A INPUT -p udp -m udp --dport 5432 -j postgres");
254             $this->exec("{$iptables} -A INPUT -p tcp -m udp --dport 5432 -j postgres");
255         }
256         
257         
258         $rows = $this->readChain('postgres');
259         if ($rows === false) {
260             $this->createBase();
261             $rows = array();
262         }
263          
264         $lastrulenum = 1;
265        
266         $remove = array();
267         $cur = array();
268          
269         foreach($rows as $row) {
270              
271            // print_r($row);
272             //var_dump($row['target']);
273             if ($row['target'] != 'ACCEPT') {
274                 continue;
275             }
276             
277              
278             if (!empty($row['expires'])) {
279                 if (strtotime($row['expires']) < time()) {
280                     $remove[ $row['source'] ] = $row;
281                 }
282             }
283             
284             $cur[ $row['source'] ] = $row;
285             
286             $lastrulenum = $row['num'];
287             
288         }
289          
290         
291         //print_r($cur);
292         //--comment
293         
294           
295          
296         foreach($this->ips as $ip=>$expires) {
297             $comment = strlen($expires) ?
298                     ('-m comment --comment ' . escapeshellarg(json_encode(array('expires'=>$expires))) ) :
299                     '';
300
301             
302             $old = isset($cur[$ip]) ? $cur[$ip] : false;
303             if ($old) {
304                 if (empty($old['expires'])) {
305                     continue;
306                 }
307                 if (strtotime($expires) <= strtotime($old['expires'])) {
308                     // expires time is the same..
309                     //?? make sure it's not flagged for removal..
310                     
311                     continue;
312                 }
313             }
314             
315             if ($old) {
316                 $this->exec("{$iptables} -R postgres {$old['num']} -s {$ip}/32 -j ACCEPT   $comment");
317                 
318                 if (isset($remove[$ip])) {
319                     unset($remove[$ip]);
320                 }
321                 continue;
322             }
323             
324             $this->exec("{$iptables} -I postgres {$lastrulenum} -s {$ip}/32 -j ACCEPT  $comment");
325                     
326             
327                                    
328         }
329         
330         // remove rules that need deleting..
331         foreach($remove as $ip => $r) {
332             
333             $this->removeIp($ip);
334              
335             
336         }
337         
338         $this->exec("{$iptables} -L postgres -v -n --line-numbers");   
339        
340   
341         
342     }
343     
344     function removeIp($ip)
345     {
346         static $iptables;
347         
348         if (!$iptables) {
349             require_once 'System.php';
350             
351             $iptables = System::which('iptables', '/sbin/iptables');
352             
353             if (!$iptables || !file_exists($iptables)) {
354                 $this->jerr("iptables could not be found.");
355             }
356         }    
357         // we need to scan the list each time, as the order get's renumbered when we remove wone...
358         $ar = $this->readChain('postgres');
359         foreach($ar as $row) {
360             if ($row['target'] != 'ACCEPT') {
361                 continue;
362             }
363             
364             if ($row['source'] != $ip) {
365                 continue;
366             }
367             $this->exec("{$iptables} -D postgres {$row['num']} ");
368             break;
369         }
370     }
371     
372     
373     
374     function createBase()
375     {
376         
377          require_once 'System.php';
378         
379         $iptables = System::which('iptables', '/sbin/iptables');
380         
381          if (!$iptables || !file_exists($iptables)) {
382             $this->jerr("iptables could not be found.");
383         }
384         
385         
386         
387         //$this->exec("{$iptables} -F postgres"); // flush old
388         $this->exec("{$iptables} -X postgres"); // flush old
389         
390         $this->exec("{$iptables} -N postgres");  // create new..
391         
392         $this->exec($iptables. ' -A postgres -m limit --limit 2/min -j LOG '.
393                         '--log-prefix "IPTables-Dropped: " --log-level 4');
394         $this->exec("$iptables -A postgres -j DROP");  
395
396         
397         
398         
399         
400     }
401     
402     function exec($cmd)
403     {
404         echo "$cmd\n";
405         $ret =  `$cmd`;
406         echo $ret."\n";
407         return $ret;
408     }
409     
410 }