ubuntuusers.de

Anhang: anfd

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
#!/usr/bin/perl -w
#
# anfd - Ain't no firewall daemon
#
# Autoren: ostcar und otzenpunk
# Homepage: http://wiki.UbuntuUsers.de/Skripte/anfd
# Lizenz: Gnu GPL Version 2 oder höher
#
# Changelog:
#    18.11.2006: v0.1
#                initial release

use strict;

use POSIX;
use Getopt::Std;
use IPTables::IPv4::IPQueue qw(:constants);
use Net::CIDR qw(:all);
use Net::RawIP;

use constant VERSION =>"0.1";

my @IP_PROTO ;
$IP_PROTO[0]='IP';       # Dummy protocol for IP
$IP_PROTO[1]='ICMP';			# Internet Control Message Protocol
$IP_PROTO[2]='IGMP';			# Internet Group Management Protocol
$IP_PROTO[4]='IPIP';			# IP in IP encapsulation
$IP_PROTO[6]='TCP';			# Transmission Control Protocol
$IP_PROTO[17]='UDP';			# User Datagram Protocol


my %opts;
my (%allow,%forbidden);
my (%use_cmdline);
my @ipt_command = qw(/sbin/iptables -I OUTPUT -m state --state NEW -j QUEUE);
my $ipt_done;   # wichtig für cleanup
my $default_config = '/etc/anfd.conf';
my $pid_file = '/var/run/anfd.pid';

getopts('kph?Dxc:i:', \%opts);
$opts{'h'} || $opts{'?'} and usage();

$< and info("You must be root to use $0.",4);
$opts{'k'} and killUs();               # We'll never come back from there

sub cleanup { #in dieser funktion kein info() verwenden, da es zu einer endlosschleife führen könnte
	print ("\tReceiving Term-Signal. Cleaning up.\n");
	if ($ipt_done) {
		$ipt_command[1] = '-D';
		print("\tResetting iptables.\n");
		system(@ipt_command) == 0
			or print("\t\tWarning: iptables failed: $?\n");
	}
	if (-e $pid_file) {
		print("\tRemoving pid file.\n");
		unlink $pid_file or print("\t\tWarning: Can't remove pid file $pid_file.\n\n");
	}
	print("Exiting.\n");
	exit;
}
	
$SIG{INT}=$SIG{TERM}=$SIG{HUP}=\&cleanup;

$pid_file = $opts{'p'} if $opts{'p'};
@ipt_command = split " ", $opts{'i'} if $opts{'i'};

(-e $pid_file) && die("Pid-File already exists. Maybe another anfd running?\n");
$opts{D} ? info("Starte anfd v".VERSION,1) : print "Starte anfd v".VERSION."\n";

readConfigFile($opts{c} || $default_config) or
		info("Empty configuration file", $opts{D} ? 0 : 4);

unless ($opts{'x'}) {
	system('modprobe', 'ip_queue');
	system(@ipt_command) == 0
		or info("iptables failed: $?",4);
	$ipt_done = 1;
}

my($ipversion, $proto,$src_ip ,$dest_ip,$dest_port, $src_port,$proc,$cmdline);
my $queue = new IPTables::IPv4::IPQueue(copy_mode => IPQ_COPY_PACKET,	copy_range => 2048)
	or info(IPTables::IPv4::IPQueue->errstr,4);

# "Daemonisieren" - vom Terminal abkoppeln
# Perl-Kochbuch: (Seite 754ff)
unless ($opts{'D'}) {
	my $pid=fork;
	exit if $pid;
	info("Error: Can't fork(): $!",4)unless defined($pid);
	writePid();
	for my $handle (*STDIN, *STDOUT, *STDERR){
		open($handle, "+<", "/dev/null")
			or info("Could not redirect $handle to /dev/null: $!",4);
	}
	POSIX::setsid()
		or info("Could not start new session: $!",4);
}  else { writePid(); }

# Endlosschleife:
while(1) { 
	# Paket aus der Queue holen
	my $msg = $queue->get_message();
	if (!defined $msg) {
		next if IPTables::IPv4::IPQueue->errstr eq 'Timeout';
		info(IPTables::IPv4::IPQueue->errstr,4);
	}
	info("Packet arrived",1);
	info("parse",1);
	($ipversion, $proto, $src_ip, $dest_ip, $src_port, $dest_port)=ripPayload($msg->payload()) if $msg->data_len();
	$proto=$IP_PROTO[$proto] || 'Unknown';

	info("IP-Version: $ipversion");
	info("Protocol: $proto");
	info("Dest-IP: $dest_ip");
	info("Dest-Port: $dest_port");
	if ($ipversion == 4) {
		if ($proto eq 'TCP' || $proto eq 'UDP') {
			($proc,$cmdline)=getProcName($src_port,$proto);
			if ($proc) {
				info ("Path: $proc");
				info ("cmdLine: $cmdline");
				info ("",2);
				info ("checking rules...",1);
				if(canPass($proc,$cmdline,$dest_ip)){
					info ("can pass");
					$queue->set_verdict($msg->packet_id, NF_ACCEPT);
				} else {
					info("can not pass");
					sendReject($msg->payload(),$src_ip);
					$queue->set_verdict($msg->packet_id, NF_DROP);
				}
				info("",2);
				info("Done",2);
				next;
			} else {        # no process id
				info("can not spy out the name",3);
			}
		} else {        # no tcp/udp
			info("$proto not supported with anfd v".VERSION,3);
		}
	} else {        # IPv6 et al.
		info("IPv$ipversion not supported with anfd v".VERSION,3);
	}
	info("",3);     # accept evt. unknown so wont break anything
	$queue->set_verdict($msg->packet_id, NF_ACCEPT);
}

#function zu einladen der conf datei
sub readConfigFile { 			# readconfigfile($configfile)
	my $file = shift;
	my $inuse;
	info("Load config file",1);
	open(CF,"$file") 
		or info("Warning: can not open $file",4);
	while(<CF>){
		my($proc, @ip, @allow, @forbidden);
		next if /^\s*(?:$|#)/;		# Bei leerer Zeile gar nicht erst weiter umwandeln
		s/\s*#.*//;								# Löscht von # bis Zeilenende
		($proc, @ip)=split();			#alle wörter der Zeile durch whitespaces trennen

		info("Config error: $_", 4) if !defined($ip[0]); # fehler, wenn nicht beide argumente vorhanden sind

		$proc =~ s/^\^// && ( $use_cmdline{$proc} = 1 ); # bei ^ am anfang cmdline benutzen
		for (@ip) {
			my @ipranges; my @h;
			if (s/^!//) {
				info("$proc can pass to $_");
				@ipranges = ((@h = validIPs($_)) ? range2cidr(@h) : ()) or info("Config error: $_", 4);
				push @allow, @ipranges;
			} elsif (/^\*$/) {
				info("$proc can not pass by default");
				push @forbidden, '0.0.0.0/0';
				$inuse=1;
			} else {
				info("$proc can not pass to $_");
				@ipranges = ((@h = validIPs($_)) ? range2cidr(@h) : ()) or info("Config error: $_", 4);
				push @forbidden, @ipranges;
				$inuse=1;
			}
		}
		$forbidden{$proc}=\@forbidden;
		$allow{$proc}=\@allow;
	}
	info("Done",2);
	return $inuse;
}

sub validIPs {
	for (@_) {
		next if cidrvalidate($_);
		if ( /^([0-9.]+)\/([0-9]+)$/ ) {
			next if cidrvalidate($1) && $2 <= 32;
		}
		if ( /^([0-9.]+)-([0-9.]+)$/ ) {
			next if cidrvalidate($1) && cidrvalidate($2);
		}
		return 0;  # wenn es in kein Schema passt, ist es falsch
	}
	return (@_);   # wenn wir hier ankommen, ist alles in Ordnung
}

#function zum Regel abgleichen
sub canPass{				# $bool = can_pass($programmname, $cmdline, $ipadresse)
										# return 1: kann passieren
										# return 0: blockieren
	my $proc=shift;
	my $cmdline=shift;
	my $ip=shift;
	
	my(@forbidden,@allow,@cmd);
	#alle argumente überprüfen, und auch des program ohne pfad
	@cmd=split('\0',$cmdline);
	for (@cmd){
		if(defined($use_cmdline{$_})||defined($forbidden{$_})||defined($allow{$_})){
			@forbidden=@{$forbidden{$_}};
			@allow=@{$allow{$_}};
		}
	}
	
	@forbidden=@{$forbidden{$proc}} if(defined($forbidden{$proc}));
	@allow=@{$allow{$proc}} if(defined($allow{$proc}));

	# Regel: Verbieten, wenn in @forbidden, aber nicht in @allow
	if(@forbidden && cidrlookup($ip,@forbidden)){
		if(@allow && cidrlookup($ip,@allow)){
			return 1;
		}else{
			return 0;
		}
	}else{
		return 1;
	}
}

#nimmt einen quellport und gibt die inode zurück bzw 0 für fehler
sub srcPort2inode{ 			# $inode = quellporttoinode($quellport,$protocol)
												# Fehler: return 0;
	my $src_port=shift;
	my $protocol=lc(shift);
	open(FILE, "/proc/net/$protocol") 
		or return 0;
	while(<FILE>) {
		my @cline=split;
		next if ($cline[9]=~/uid/);
		my $port = hex((split(":",$cline[1]))[1]);
		if($port==$src_port){
			close(FILE);
			return $cline[9];
		}
	}
	close(FILE);
	return 0;
}

#nimmt eine inode und gibt eine prog nr zurück
sub inode2procNr{ 				# $procnr = inodetoprocnr($inode);
													# bei Fehler: return 0;
	my $inode=shift;
	return 0 unless $inode;
	my($proc,$ports,$dev,$ino,$link);
	opendir (PROC, "/proc") 
		or return 0;
	for $proc (readdir(PROC)) {              # Infos zu jedem laufenden Programm liefert /proc
	                                         # Wir müssen alle testen, bis eins passt.
		next if(!($proc=~/[0-9]+/) );          # Nach PIDs suchen.
		if(!opendir(PORTS,"/proc/$proc/fd")) { # fd-Ordner öffnen
			closedir PORTS;
			next;                                # nächstes Programm, wenn Öffnen fehlschlägt
		}
		for $ports (readdir(PORTS)) { #in jedem laufenden program die geöffneten dateien durchsuchen
			next if(!($ports=~/[0-9]+/)); #muss zahlen beinhalten
			$link=readlink("/proc/$proc/fd/$ports"); #zu der datei gehn, auf welches die nummer zeigt
			($dev,$ino)=($link=~/^(socket|\[[0-9a-fA-F]*\]):\[?([0-9]*)\]?$/);
			if(defined($ino)&& defined($dev) && $ino==$inode && ($dev eq "[0000]" || $dev eq "socket")){
				closedir PORTS;
				closedir PROC;
				return $proc;
			}
		}
		closedir PORTS;
	}
	closedir PROC;
	return 0;
}

#nimmt einen srcPort und gibt den programnamen und die cmdline aus
sub getProcName{									# ($pfad, $cmdline) = getprocname($quellport,$protocol);
					 												# Fehler: return 0;
	my $procnr=inode2procNr(srcPort2inode(@_));
	
	return 0 unless $procnr;
	my($pfad,$cmdline,@cmdline);
	$pfad=readlink("/proc/$procnr/exe");
	open(CMDLINE,"/proc/$procnr/cmdline") or return 0;
	$cmdline=<CMDLINE>;
	@cmdline=split(" ",$cmdline);
	$cmdline[0]=~s/^\/.*\///;
	$cmdline=join(" ",@cmdline);
	close(CMDLINE);
	return $pfad, $cmdline;
}

# Nimmt ein TCP/IP-Packet und liefert IP-Version (norm. 4), Protokoll (TCP/UDP als Nummer) IPs und Ports zurück
sub ripPayload {
   my $payload = shift;

   my ($version, $proto, $src_ip, $dest_ip, $src_port, $dest_port);
   my (@src_ip, @dest_ip);
                                         # IP-Header auseinanderpflücken:
	($version, undef, $proto, undef, @src_ip[0..3], @dest_ip[0..3], $src_port, $dest_port) =
     unpack("C1a8C1a2C8n1n1", $payload);

	$version >>= 4;                             # IPv steht in linken vier Bits
   $src_ip = join ".", @src_ip;
   $dest_ip = join ".", @dest_ip;

   return ($version, $proto, $src_ip, $dest_ip, $src_port, $dest_port);
}

# IP_QUEUE kennt nur ACCEPT und DROP. Deswegen konstruieren wir unser eigenes Reject-Paket
sub sendReject {
	my $payload = unpack("a28", shift);   # Anfang des Originalpakets wird in ICMP verpackt
	my $ip = shift;
	Net::RawIP->new({ip => {saddr => $ip, daddr => $ip},
									icmp => {type => 3, code => 0, data => $payload}
									})->send();                             # network unreachable
}
INIT {
	my $ebene = 0;
	sub info {
		my $info = shift;
		my $command = shift || 0;
		unless ($opts{D}) {
			if ($command == 4) {
				print "$info\n";
				cleanup();
			}
			return;
		}
		if ($command==1){ #neue ebene
			print "\t" x $ebene.$info.": \n";
			$ebene++;
		}elsif ($command==2){ #ebene runter
			$ebene--;
			print "\t" x $ebene  .$info."\n" if $info;
		}elsif($command==3){ #ebene runter mit fehler
			$ebene--;
			print "\t" x $ebene."Error".($info?": $info":"")."\n";
		}elsif($command==4){ #alle ebenen runter mit fehler und ende
			$ebene--;
			for(;$ebene>0;$ebene--){
				print "\t" x $ebene."Critical Error".($info?": $info":"")."\n";
			}
			cleanup();
		}else{
			print "\t" x $ebene.$info."\n"
		}
	}
}

sub usage {
	my $ipt = join " ", @ipt_command;
	print <<EOT;
$0 - Ain't no firewall daemon.

anfd [-D] [-i 'iptables command'] [-x] [-c configfile] [-p pidfile]
anfd -k [-p pidfile]
anfd (-h|-?)

Anfd is a userspace daemon that uses the netfilter-ip_queue mechanism to hinder
specific software from "phoning home". It is not security software but privacy
software.

Options:
	-h, -?      : Print this help message.
	-D          : Debug mode - don't detach from terminal and print detailed infos
	-i 'command': Use this iptables command to insert the QUEUE rule.
	              Default: $ipt
	-x          : Dont insert any iptables rule. Admin will take care of that
	              herself.
	-c file     : Use this config file. Default: $default_config
	-p pidfile  : Use this pid file. Default: $pid_file
	-k          : Kill running anfd process.
EOT
	exit 1;
}

sub killUs {
	open PID, "< $pid_file"
		or die("Can't open pid file. Either there is no running anfd process or you'll have to kill running anfd processes yourself.\n");
	my $pid = <PID>; close PID;
	($pid) = split '\D+', $pid;
	$pid or die("No pid found in $pid_file. You'll have to kill running anfd processes yourself.\n");
	unless (kill 'TERM', $pid) {
		print("No process $pid.  You'll have to kill running anfd processes yourself.\n");
		unlink $pid_file or print "Can't delete $pid_file.\n";
	}
	print("Killed anfd.\n");
	exit;
}

sub writePid {
	if (-e $pid_file) {
		info("There seems to be another anfd process running. Use anfd -k to get rid of it.",4);
	} else {
		open PID, "> $pid_file" or info("Can not write pid file.\n",4);
		print PID $$; close PID;
	}
}
Anhang herunterladen

Diese Revision wurde am 2. Oktober 2018 13:28 von ubuntuusers erstellt.