2323import socket
2424import logging
2525import gc
26+ import re
27+ import os
28+ import sys
29+ import struct
30+ import select
31+ from itertools import islice
32+ from multiprocessing import Process , Queue
2633
2734class NMAP (object ):
35+ ICMP_ECHO_REQUEST = 8
36+
2837 def __init__ (self ):
2938 pass
3039
@@ -43,32 +52,24 @@ def get_sensordef():
4352 sensordefinition = {
4453 "kind" : NMAP .get_kind (),
4554 "name" : "NMAP" ,
46- "description" : "Checks the availability of a port (range) on one or more systems." ,
47- "help" : "Checks the availability of a port (range) on one or more systems and logs this to a separate logfile on the miniprobe." ,
55+ "description" : "Checks the availability of systems." ,
56+ "help" : "Checks the availability of systems on a network and logs this to a separate logfile on the miniprobe." ,
4857 "tag" : "mpnmapsensor" ,
4958 "groups" : [
5059 {
51- "name" : "portspecific " ,
52- "caption" : "Port specific" ,
60+ "name" : "nmapspecific " ,
61+ "caption" : "NMAP specific" ,
5362 "fields" : [
5463 {
5564 "type" : "integer" ,
5665 "name" : "timeout" ,
57- "caption" : "Timeout (in s )" ,
66+ "caption" : "Timeout (in ms )" ,
5867 "required" : "1" ,
59- "default" : 60 ,
60- "minimum" : 1 ,
61- "maximum" : 900 ,
68+ "default" : 50 ,
69+ "minimum" : 10 ,
70+ "maximum" : 1000 ,
6271 "help" : "If the reply takes longer than this value the request is aborted "
63- "and an error message is triggered. Max. value is 900 sec. (=15 min.)"
64- },
65- {
66- "type" : "edit" ,
67- "name" : "port" ,
68- "caption" : "Port or port range" ,
69- "required" : "1" ,
70- "default" : "1-1024" ,
71- "help" : "Specify the port or the port range devided by a - (for example: 1-1024)"
72+ "and an error message is triggered. Max. value is 1000 ms. (=1 sec.)"
7273 },
7374 {
7475 "type" : "edit" ,
@@ -79,83 +80,221 @@ def get_sensordef():
7980 "help" : "Specify the ip-address or a range of addresses using one of the following notations:[br]Single: 192.168.1.1[br]CIDR: 192.168.1.0/24[br]- separated: 192.168.1.1-192.168.1.100"
8081 }
8182 ]
82- },
83- {
84- "name" : "mail" ,
85- "caption" : "Mail Settings" ,
86- "fields" : [
87- {
88- "type" : "edit" ,
89- "name" : "email" ,
90- "caption" : "E-Mailaddress" ,
91- "required" : "1" ,
92- "help" : "Specify the e-mailaddress to which the NMAP report should be sent to when the scanning has been finished"
93- }
94- ]
9583 }
9684 ]
9785 }
9886 return sensordefinition
9987
100- def portrange (self , target , timeout , start , end ):
101- remote_server = socket .gethostbyname (target )
102- numberofports = int (end ) - int (start )
103- result = 1234
104- a = 0
105- start_time = time .time ()
106- for port in range (int (start ), int (end )):
107- try :
108- conn = socket .socket (socket .AF_INET , socket .SOCK_STREAM )
109- conn .settimeout (float (timeout ))
110- result = conn .connect_ex ((remote_server , int (port )))
111- conn .close ()
112- except socket .gaierror as e :
113- print e
114- except socket .timeout as e :
115- print e
116- except Exception as e :
117- print "test %s" % e
118- if result == 0 :
119- a += 1
120- else :
121- raise Exception ('port %s not open' % port )
122-
123- end_time = time .time ()
124- response_time = (end_time - start_time ) * 1000
125- if a == numberofports :
126- channel_list = [
127- {
128- "name" : "Available" ,
129- "mode" : "float" ,
130- "kind" : "TimeResponse" ,
131- "value" : float (response_time )
132- }
133- ]
134- return channel_list
135- else :
136- raise Exception
137-
13888 @staticmethod
139- def get_data (data ):
140- port = NMAP ()
89+ def get_data (data , out_queue ):
90+ nmap = NMAP ()
91+ error = False
92+ tmpMessage = ""
93+ cidr = ""
94+ alive_str = ""
95+ alive_cnt = 0
14196 try :
142- port_data = port .portrange (data ['host' ], data ['timeout' ], data ['startport' ], data ['endport' ])
143- logging .debug ("Running sensor: %s" % port .get_kind ())
97+ logging .debug ("Running sensor: %s" % nmap .get_kind ())
98+ if '/' in data ['ip' ]:
99+ validCIDR = nmap .validateCIDRBlock (data ['ip' ])
100+ if validCIDR :
101+ tmpMessage = "True"
102+ cidr = nmap .returnCIDR (data ['ip' ])
103+ for ip in islice (cidr , 1 , len (cidr )- 1 ):
104+ result = nmap .do_one_ping (ip , float (data ['timeout' ])/ 1000 )
105+ if not result == None :
106+ alive_str = alive_str + ip + ": " + str (int (result * 1000 )) + " ms, "
107+ alive_cnt = alive_cnt + 1
108+ else :
109+ tmpMessage = validCIDR
110+ error = True
111+ channel_list = [
112+ {
113+ "name" : "Hosts alive" ,
114+ "mode" : "Integer" ,
115+ "kind" : "count" ,
116+ "value" : alive_cnt
117+ }]
144118 except Exception as e :
145- logging .error ("Ooops Something went wrong with '%s' sensor %s. Error: %s" % (port .get_kind (),
119+ logging .error ("Ooops Something went wrong with '%s' sensor %s. Error: %s" % (nmap .get_kind (),
146120 data ['sensorid' ], e ))
147121 sensor_data = {
148122 "sensorid" : int (data ['sensorid' ]),
149123 "error" : "Exception" ,
150124 "code" : 1 ,
151125 "message" : "Port check failed or ports closed. See log for details"
152126 }
153- return sensor_data
127+ out_queue .put (sensor_data )
128+ return
154129 sensor_data = {
155130 "sensorid" : int (data ['sensorid' ]),
156- "message" : "OK Ports open" ,
157- "channel" : port_data
131+ "message" : alive_str [: - 2 ] ,
132+ "channel" : channel_list
158133 }
159- del port
134+ del nmap
160135 gc .collect ()
161- return sensor_data
136+ out_queue .put (sensor_data )
137+
138+ def ip2bin (self ,ip ):
139+ b = ""
140+ nmap = NMAP ()
141+ inQuads = ip .split ("." )
142+ outQuads = 4
143+ for q in inQuads :
144+ if q != "" :
145+ b += nmap .dec2bin (int (q ),8 )
146+ outQuads -= 1
147+ while outQuads > 0 :
148+ b += "00000000"
149+ outQuads -= 1
150+ return b
151+
152+ def dec2bin (self ,n ,d = None ):
153+ s = ""
154+ while n > 0 :
155+ if n & 1 :
156+ s = "1" + s
157+ else :
158+ s = "0" + s
159+ n >>= 1
160+ if d is not None :
161+ while len (s )< d :
162+ s = "0" + s
163+ if s == "" : s = "0"
164+ return s
165+
166+ def bin2ip (self ,b ):
167+ ip = ""
168+ for i in range (0 ,len (b ),8 ):
169+ ip += str (int (b [i :i + 8 ],2 ))+ "."
170+ return ip [:- 1 ]
171+
172+ def validateCIDRBlock (self ,b ):
173+ # appropriate format for CIDR block ($prefix/$subnet)
174+ p = re .compile ("^([0-9]{1,3}\.){0,3}[0-9]{1,3}(/[0-9]{1,2}){1}$" )
175+ if not p .match (b ):
176+ return "Error: Invalid CIDR format!"
177+ # extract prefix and subnet size
178+ prefix , subnet = b .split ("/" )
179+ # each quad has an appropriate value (1-255)
180+ quads = prefix .split ("." )
181+ for q in quads :
182+ if (int (q ) < 0 ) or (int (q ) > 255 ):
183+ return "Error: quad " + str (q )+ " wrong size."
184+ # subnet is an appropriate value (1-32)
185+ if (int (subnet ) < 1 ) or (int (subnet ) > 32 ):
186+ return "Error: subnet " + str (subnet )+ " wrong size."
187+ # passed all checks -> return True
188+ return True
189+
190+ def returnCIDR (self ,c ):
191+ nmap = NMAP ()
192+ ips = []
193+ parts = c .split ("/" )
194+ baseIP = nmap .ip2bin (parts [0 ])
195+ subnet = int (parts [1 ])
196+ # Python string-slicing weirdness:
197+ # "myString"[:-1] -> "myStrin" but "myString"[:0] -> ""
198+ # if a subnet of 32 was specified simply print the single IP
199+ if subnet == 32 :
200+ return nmap .bin2ip (baseIP )
201+ # for any other size subnet, print a list of IP addresses by concatenating
202+ # the prefix with each of the suffixes in the subnet
203+ else :
204+ ipPrefix = baseIP [:- (32 - subnet )]
205+ for i in range (2 ** (32 - subnet )):
206+ ips .append (nmap .bin2ip (ipPrefix + nmap .dec2bin (i , (32 - subnet ))))
207+ return ips
208+
209+ def checksum (self , source_string ):
210+ """
211+ I'm not too confident that this is right but testing seems
212+ to suggest that it gives the same answers as in_cksum in ping.c
213+ """
214+ sum = 0
215+ countTo = (len (source_string )/ 2 )* 2
216+ count = 0
217+ while count < countTo :
218+ thisVal = ord (source_string [count + 1 ])* 256 + ord (source_string [count ])
219+ sum = sum + thisVal
220+ sum = sum & 0xffffffff # Necessary?
221+ count = count + 2
222+ if countTo < len (source_string ):
223+ sum = sum + ord (source_string [len (source_string ) - 1 ])
224+ sum = sum & 0xffffffff # Necessary?
225+ sum = (sum >> 16 ) + (sum & 0xffff )
226+ sum = sum + (sum >> 16 )
227+ answer = ~ sum
228+ answer = answer & 0xffff
229+ # Swap bytes. Bugger me if I know why.
230+ answer = answer >> 8 | (answer << 8 & 0xff00 )
231+ return answer
232+
233+ def receive_one_ping (self , my_socket , ID , timeout ):
234+ """
235+ receive the ping from the socket.
236+ """
237+ timeLeft = float (timeout )
238+ while True :
239+ startedSelect = time .time ()
240+ whatReady = select .select ([my_socket ], [], [], timeLeft )
241+ howLongInSelect = (time .time () - startedSelect )
242+ if whatReady [0 ] == []: # Timeout
243+ return
244+ timeReceived = time .time ()
245+ recPacket , addr = my_socket .recvfrom (1024 )
246+ icmpHeader = recPacket [20 :28 ]
247+ type , code , checksum , packetID , sequence = struct .unpack (
248+ "bbHHh" , icmpHeader
249+ )
250+ if packetID == ID :
251+ bytesInDouble = struct .calcsize ("d" )
252+ timeSent = struct .unpack ("d" , recPacket [28 :28 + bytesInDouble ])[0 ]
253+ return timeReceived - timeSent
254+ timeLeft = timeLeft - howLongInSelect
255+ if timeLeft <= 0 :
256+ return
257+
258+ def send_one_ping (self , my_socket , dest_addr , ID ):
259+ """
260+ Send one ping to the given >dest_addr<.
261+ """
262+ dest_addr = socket .gethostbyname (dest_addr )
263+ # Header is type (8), code (8), checksum (16), id (16), sequence (16)
264+ my_checksum = 0
265+ # Make a dummy heder with a 0 checksum.
266+ header = struct .pack ("bbHHh" , self .ICMP_ECHO_REQUEST , 0 , my_checksum , ID , 1 )
267+ bytesInDouble = struct .calcsize ("d" )
268+ data = (192 - bytesInDouble ) * "Q"
269+ data = struct .pack ("d" , time .time ()) + data
270+ # Calculate the checksum on the data and the dummy header.
271+ my_checksum = self .checksum (header + data )
272+ # Now that we have the right checksum, we put that in. It's just easier
273+ # to make up a new header than to stuff it into the dummy.
274+ header = struct .pack (
275+ "bbHHh" , self .ICMP_ECHO_REQUEST , 0 , socket .htons (my_checksum ), ID , 1
276+ )
277+ packet = header + data
278+ my_socket .sendto (packet , (dest_addr , 1 )) # Don't know about the 1
279+
280+ def do_one_ping (self , dest_addr , timeout ):
281+ """
282+ Returns either the delay (in seconds) or none on timeout.
283+ """
284+ icmp = socket .getprotobyname ("icmp" )
285+ try :
286+ my_socket = socket .socket (socket .AF_INET , socket .SOCK_RAW , icmp )
287+ except socket .error , (errno , msg ):
288+ if errno == 1 :
289+ # Operation not permitted
290+ msg = msg + (
291+ " - Note that ICMP messages can only be sent from processes"
292+ " running as root."
293+ )
294+ raise socket .error (msg )
295+ raise # raise the original error
296+ my_ID = os .getpid () & 0xFFFF
297+ self .send_one_ping (my_socket , dest_addr , my_ID )
298+ delay = self .receive_one_ping (my_socket , my_ID , timeout )
299+ my_socket .close ()
300+ return delay
0 commit comments