Commit 55a73f12f2d9a0b09e25505f60baf2d9e2ba4019
Committed by
Eduardo Santos
Exists in
master
Versão que já é capaz de armazenar as impressoras e seus contadores no banco de dados.
Showing
8 changed files
with
433 additions
and
16 deletions
Show diff stats
cocar/commands/scan_commands.py
... | ... | @@ -4,13 +4,20 @@ __author__ = 'eduardo' |
4 | 4 | import logging |
5 | 5 | import os |
6 | 6 | import os.path |
7 | +import lxml.etree | |
8 | +import time | |
9 | +import pickle | |
7 | 10 | from paste.script import command |
8 | 11 | from .. import Cocar |
9 | 12 | from ..model import Base |
10 | 13 | from ..model.network import Network |
14 | +from ..model.printer import Printer, PrinterCounter | |
15 | +from ..model.host import Host | |
16 | +from ..model.computer import Computer | |
11 | 17 | from ..csv_utils import NetworkCSV |
12 | -from ..session import NmapSession | |
18 | +from ..session import NmapSession, SnmpSession | |
13 | 19 | from multiprocessing import Process, Queue |
20 | +from ..xml_utils import NmapXML | |
14 | 21 | |
15 | 22 | log = logging.getLogger() |
16 | 23 | |
... | ... | @@ -26,7 +33,7 @@ class ScanCommands(command.Command): |
26 | 33 | paster scan networks -c <path to config file> |
27 | 34 | - Faz a busca das redes |
28 | 35 | |
29 | - Os comandos devem ser executados a partir da raiz do módulo Cocar | |
36 | + Os comandos devem ser executados a partir da raiz do modulo Cocar | |
30 | 37 | """ |
31 | 38 | max_args = 1 |
32 | 39 | min_args = 1 |
... | ... | @@ -77,6 +84,15 @@ class ScanCommands(command.Command): |
77 | 84 | if cmd == 'scan_networks': |
78 | 85 | self.scan_networks() |
79 | 86 | return |
87 | + if cmd == 'load_network_files': | |
88 | + self.load_network_files() | |
89 | + return | |
90 | + if cmd == 'get_printers': | |
91 | + self.get_printers() | |
92 | + return | |
93 | + if cmd == 'continous_scan': | |
94 | + self.continuous_scan() | |
95 | + return | |
80 | 96 | else: |
81 | 97 | log.error('Command "%s" not recognized' % (cmd,)) |
82 | 98 | |
... | ... | @@ -148,6 +164,123 @@ class ScanCommands(command.Command): |
148 | 164 | for i in range(processes): |
149 | 165 | task_queue.put('STOP') |
150 | 166 | |
167 | + def load_network_files(self): | |
168 | + """ | |
169 | + Load printers from networks files | |
170 | + :return: | |
171 | + """ | |
172 | + onlyfiles = [ f for f in os.listdir(self.networks_dir) if os.path.isfile(os.path.join(self.networks_dir, f)) ] | |
173 | + for i in range(len(onlyfiles)): | |
174 | + network_file = self.networks_dir + "/" + onlyfiles[i] | |
175 | + log.info("Processando arquivo de rede %s", network_file) | |
176 | + nmap_xml = NmapXML(network_file) | |
177 | + try: | |
178 | + host_dict = nmap_xml.parse_xml() | |
179 | + except AttributeError, e: | |
180 | + log.error("Erro realizando parsing do arquivo %s\n%s", network_file, e.message) | |
181 | + continue | |
182 | + except lxml.etree.XMLSyntaxError, e: | |
183 | + log.error("Erro realizando parsing do arquivo %s\n%s", network_file, e.message) | |
184 | + continue | |
185 | + | |
186 | + if not host_dict: | |
187 | + log.error("File %s not found", network_file) | |
188 | + continue | |
189 | + session = self.cocar.Session | |
190 | + for hostname in nmap_xml.hosts.keys(): | |
191 | + host = nmap_xml.identify_host(hostname) | |
192 | + if isinstance(host, Printer): | |
193 | + # Vê se a impressora já está na base | |
194 | + results = session.query(Printer).filter(Printer.network_ip == hostname).first() | |
195 | + if results is None: | |
196 | + log.info("Inserindo impressora com o IP %s", hostname) | |
197 | + session.add(host) | |
198 | + else: | |
199 | + log.info("Impressora com o IP %s já cadastrada", hostname) | |
200 | + elif isinstance(host, Computer): | |
201 | + # Vê se o host já está na base | |
202 | + results = session.query(Computer).filter(Computer.network_ip == hostname).first() | |
203 | + if results is None: | |
204 | + log.info("Inserindo computador com o IP %s", hostname) | |
205 | + session.add(host) | |
206 | + else: | |
207 | + log.info("Computador com o IP %s já cadastrado", hostname) | |
208 | + else: | |
209 | + # Insere host genérico | |
210 | + results = session.query(Host).filter(Host.network_ip == hostname).first() | |
211 | + if results is None: | |
212 | + log.info("Inserindo host genérico com o IP %s", hostname) | |
213 | + session.add(host) | |
214 | + else: | |
215 | + log.info("Host genérico com o IP %s já cadastrado", hostname) | |
216 | + | |
217 | + session.flush() | |
218 | + | |
219 | + def get_printers(self): | |
220 | + """ | |
221 | + Read printers SNMP Information | |
222 | + :return: | |
223 | + """ | |
224 | + processes = int(self.cocar.config.get('cocar', 'processes')) | |
225 | + # Create queues | |
226 | + task_queue = Queue() | |
227 | + done_queue = Queue() | |
228 | + | |
229 | + session = self.cocar.Session | |
230 | + results = session.query(Printer).all() | |
231 | + for printer in results: | |
232 | + log.info("Coletando informacoes da impressora %s", printer.network_ip) | |
233 | + #printer.network_ip = printer.ip_network | |
234 | + snmp_session = SnmpSession( | |
235 | + DestHost=printer.network_ip | |
236 | + ) | |
237 | + if snmp_session is None: | |
238 | + log.error("Erro na coleta SNMP da impressora %s", printer.network_ip) | |
239 | + continue | |
240 | + else: | |
241 | + task_queue.put(snmp_session) | |
242 | + | |
243 | + #Start worker processes | |
244 | + for i in range(processes): | |
245 | + Process(target=worker_printer, args=(task_queue, done_queue)).start() | |
246 | + | |
247 | + # Get and print results | |
248 | + log.debug('Unordered results:') | |
249 | + for i in range(len(results)): | |
250 | + printer_dict = done_queue.get() | |
251 | + log.debug(printer_dict) | |
252 | + if printer_dict['counter'] is None: | |
253 | + log.error("Nao foi possivel ler o contador da impressora %s", printer_dict['network_ip']) | |
254 | + continue | |
255 | + | |
256 | + try: | |
257 | + log.debug("Gravando contador = %s para a impressora = %s serial = %s", printer_dict['counter'], printer_dict['network_ip'], printer_dict['serial']) | |
258 | + printer = PrinterCounter( | |
259 | + ip_address=printer_dict['network_ip'], | |
260 | + model=printer_dict['model'], | |
261 | + serial=printer_dict['serial'], | |
262 | + description=printer_dict['description'], | |
263 | + counter=printer_dict['counter'], | |
264 | + counter_time=time.time() | |
265 | + ) | |
266 | + printer.update_counter(session) | |
267 | + except AttributeError, e: | |
268 | + log.error("Erro na inserção do contador para a impressora %s\n%s", printer_dict['network_ip'], e.message) | |
269 | + continue | |
270 | + | |
271 | + # Tell child processes to stop | |
272 | + for i in range(processes): | |
273 | + task_queue.put('STOP') | |
274 | + | |
275 | + def continuous_scan(self): | |
276 | + """ | |
277 | + Fica varrendo a rede até parar por execução forçada | |
278 | + """ | |
279 | + print("*** Aperente CTRL+C para encerrar a execução ***") | |
280 | + | |
281 | + while True: | |
282 | + self.scan_networks() | |
283 | + | |
151 | 284 | |
152 | 285 | def make_query(host): |
153 | 286 | """This does the actual snmp query |
... | ... | @@ -160,8 +293,26 @@ def make_query(host): |
160 | 293 | return host.scan() |
161 | 294 | |
162 | 295 | |
296 | +def make_query_printer(host): | |
297 | + """This does the actual snmp query | |
298 | + | |
299 | + This is a bit fancy as it accepts both instances | |
300 | + of SnmpSession and host/ip addresses. This | |
301 | + allows a user to customize mass queries with | |
302 | + subsets of different hostnames and community strings | |
303 | + """ | |
304 | + return host.printer_dict() | |
305 | + | |
306 | + | |
163 | 307 | # Function run by worker processes |
164 | 308 | def worker(inp, output): |
165 | 309 | for func in iter(inp.get, 'STOP'): |
166 | 310 | result = make_query(func) |
311 | + output.put(result) | |
312 | + | |
313 | + | |
314 | +# Function run by worker processes | |
315 | +def worker_printer(inp, output): | |
316 | + for func in iter(inp.get, 'STOP'): | |
317 | + result = make_query_printer(func) | |
167 | 318 | output.put(result) |
168 | 319 | \ No newline at end of file | ... | ... |
cocar/model/host.py
... | ... | @@ -47,9 +47,15 @@ class Host(Base): |
47 | 47 | |
48 | 48 | # Parâmetros do SQLAlchemy |
49 | 49 | self.network_ip = str(self.ip_address) |
50 | - self.ports = ','.join(map(str, self.open_ports.keys())) | |
51 | - if len(self.hostname.values()) > 0: | |
52 | - self.name = self.hostname.values()[0] | |
50 | + if self.open_ports is not None: | |
51 | + self.ports = ','.join(map(str, self.open_ports.keys())) | |
52 | + else: | |
53 | + self.ports = None | |
54 | + if self.hostname is not None: | |
55 | + if len(self.hostname.values()) > 0: | |
56 | + self.name = self.hostname.values()[0] | |
57 | + else: | |
58 | + self.name = None | |
53 | 59 | else: |
54 | 60 | self.name = None |
55 | 61 | ... | ... |
cocar/model/printer.py
1 | 1 | #!/bin/env python |
2 | 2 | # -*- coding: utf-8 -*- |
3 | 3 | __author__ = 'eduardo' |
4 | - | |
4 | +import logging | |
5 | 5 | from .host import Host |
6 | 6 | from sqlalchemy import ForeignKey |
7 | 7 | from sqlalchemy.schema import Column |
8 | 8 | from sqlalchemy.types import String, Integer |
9 | +from sqlalchemy import and_, insert, update | |
10 | + | |
11 | +log = logging.getLogger() | |
9 | 12 | |
10 | 13 | |
11 | 14 | class Printer(Host): |
... | ... | @@ -14,12 +17,11 @@ class Printer(Host): |
14 | 17 | """ |
15 | 18 | __tablename__ = 'printer' |
16 | 19 | network_ip = Column(String(16), ForeignKey("host.network_ip"), nullable=False, primary_key=True) |
17 | - counter = Column(Integer) | |
20 | + model = Column(String) | |
18 | 21 | serial = Column(String(50)) |
19 | 22 | description = Column(String) |
20 | 23 | |
21 | 24 | def __init__(self, |
22 | - counter=None, | |
23 | 25 | model=None, |
24 | 26 | serial=None, |
25 | 27 | description=None, |
... | ... | @@ -32,7 +34,62 @@ class Printer(Host): |
32 | 34 | :param serial: Número de série da impressora |
33 | 35 | """ |
34 | 36 | Host.__init__(self, *args, **kwargs) |
35 | - self.counter = counter | |
36 | 37 | self.model = model |
37 | 38 | self.serial = serial |
38 | - self.description = description | |
39 | 39 | \ No newline at end of file |
40 | + self.description = description | |
41 | + | |
42 | + | |
43 | +class PrinterCounter(Printer): | |
44 | + """ | |
45 | + Classe que armazena o contador das impressoras | |
46 | + """ | |
47 | + __tablename__ = 'printer_counter' | |
48 | + network_ip = Column(String(16), ForeignKey("printer.network_ip"), nullable=False) | |
49 | + counter = Column(Integer, nullable=False, primary_key=True) | |
50 | + counter_time = Column(String(50), nullable=False, primary_key=True) | |
51 | + | |
52 | + def __init__(self, | |
53 | + counter, | |
54 | + counter_time, | |
55 | + *args, | |
56 | + **kwargs | |
57 | + ): | |
58 | + super(PrinterCounter, self).__init__(*args, **kwargs) | |
59 | + self.counter = counter | |
60 | + self.counter_time = counter_time | |
61 | + | |
62 | + def update_counter(self, session): | |
63 | + """ | |
64 | + Atualiza contador da impressora | |
65 | + :param session: SQLAlchemy session | |
66 | + :return boolean: True if inserted | |
67 | + """ | |
68 | + results = session.query(self.__table__).filter( | |
69 | + and_( | |
70 | + self.__table__.c.counter == self.counter, | |
71 | + self.__table__.c.counter_time == self.counter_time) | |
72 | + ).first() | |
73 | + print(results) | |
74 | + if results is None: | |
75 | + log.debug("Inserindo contador para impressora %s serial %s", self.network_ip, self.serial) | |
76 | + session.execute( | |
77 | + self.__table__.insert().values( | |
78 | + network_ip=self.network_ip, | |
79 | + counter=self.counter, | |
80 | + counter_time=self.counter_time | |
81 | + ) | |
82 | + ) | |
83 | + return True | |
84 | + | |
85 | + session.execute( | |
86 | + Printer.__table__.update().values( | |
87 | + model=self.model, | |
88 | + description=self.description, | |
89 | + serial=self.serial | |
90 | + ).where( | |
91 | + Printer.__table__.c.network_ip == self.network_ip | |
92 | + ) | |
93 | + ) | |
94 | + | |
95 | + session.flush() | |
96 | + return False | |
40 | 97 | \ No newline at end of file | ... | ... |
cocar/session.py
... | ... | @@ -23,7 +23,7 @@ class Host(object): |
23 | 23 | self.query = query |
24 | 24 | |
25 | 25 | |
26 | -class SnmpSession(Cocar): | |
26 | +class SnmpSession(object): | |
27 | 27 | """A SNMP Session""" |
28 | 28 | def __init__(self, |
29 | 29 | oid=".1.3.6.1.2.1.1.1.0", |
... | ... | @@ -33,7 +33,6 @@ class SnmpSession(Cocar): |
33 | 33 | Community="public", |
34 | 34 | Verbose=True, |
35 | 35 | ): |
36 | - Cocar.__init__(self) | |
37 | 36 | self.oid = oid |
38 | 37 | self.Version = Version |
39 | 38 | self.DestHost = DestHost |
... | ... | @@ -43,6 +42,12 @@ class SnmpSession(Cocar): |
43 | 42 | self.hostrec = Host() |
44 | 43 | self.hostrec.hostname = self.DestHost |
45 | 44 | |
45 | + self.status = ['1.3.6.1.2.1.25.3.5.1.1.1'] | |
46 | + self.serial = ['1.3.6.1.2.1.43.5.1.1.17'] | |
47 | + self.model = ['1.3.6.1.2.1.25.3.2.1.3.1'] | |
48 | + self.counter = ['1.3.6.1.2.1.43.10.2.1.4.1.1'] | |
49 | + self.messages = ['1.3.6.1.2.1.43.18.1.1.8'] | |
50 | + | |
46 | 51 | def query(self): |
47 | 52 | """Creates SNMP query |
48 | 53 | |
... | ... | @@ -61,6 +66,114 @@ class SnmpSession(Cocar): |
61 | 66 | finally: |
62 | 67 | return self.hostrec |
63 | 68 | |
69 | + def printer_full(self): | |
70 | + """ | |
71 | + Retorna status full da impressora, com todos os atributos | |
72 | + """ | |
73 | + status = self.query() | |
74 | + if status is None: | |
75 | + return None | |
76 | + else: | |
77 | + return status.query | |
78 | + | |
79 | + def printer_status(self): | |
80 | + """ | |
81 | + Retorna status da impressora | |
82 | + | |
83 | + Opções de status: | |
84 | + | |
85 | + 1 - unknown | |
86 | + 2 - runnning | |
87 | + 3 - warning | |
88 | + 4 - testing | |
89 | + 5 - down | |
90 | + """ | |
91 | + status = None | |
92 | + for elm in self.status: | |
93 | + self.oid = elm | |
94 | + status = self.query() | |
95 | + # A primeira vez que conseguir retornar um status, para | |
96 | + if status is not None: | |
97 | + break | |
98 | + if status is None: | |
99 | + return None | |
100 | + else: | |
101 | + return status.query | |
102 | + | |
103 | + def printer_counter(self): | |
104 | + """ | |
105 | + Retorna contador da impressora | |
106 | + """ | |
107 | + status = None | |
108 | + for elm in self.counter: | |
109 | + self.oid = elm | |
110 | + status = self.query() | |
111 | + # A primeira vez que conseguir retornar um status, para | |
112 | + if status is not None: | |
113 | + break | |
114 | + | |
115 | + if status is None: | |
116 | + return None | |
117 | + else: | |
118 | + return status.query | |
119 | + | |
120 | + def printer_model(self): | |
121 | + """ | |
122 | + Retorna contador da impressora | |
123 | + """ | |
124 | + status = None | |
125 | + for elm in self.model: | |
126 | + self.oid = elm | |
127 | + status = self.query() | |
128 | + # A primeira vez que conseguir retornar um status, para | |
129 | + if status is not None: | |
130 | + break | |
131 | + | |
132 | + if status is None: | |
133 | + return None | |
134 | + else: | |
135 | + return status.query | |
136 | + | |
137 | + def printer_serial(self): | |
138 | + """ | |
139 | + Retorna contador da impressora | |
140 | + """ | |
141 | + status = None | |
142 | + for elm in self.serial: | |
143 | + self.oid = elm | |
144 | + status = self.query() | |
145 | + # A primeira vez que conseguir retornar um status, para | |
146 | + if status is not None: | |
147 | + break | |
148 | + | |
149 | + if status is None: | |
150 | + return None | |
151 | + else: | |
152 | + return status.query | |
153 | + | |
154 | + def printer_dict(self): | |
155 | + """ | |
156 | + Retorna o status de todos os atributos em um dicionário | |
157 | + """ | |
158 | + full = self.printer_full() | |
159 | + serial = self.printer_serial() | |
160 | + model = self.printer_model() | |
161 | + counter = self.printer_counter() | |
162 | + status = self.printer_status() | |
163 | + | |
164 | + return_dict = { | |
165 | + 'description': full[0], | |
166 | + 'serial': serial[0], | |
167 | + 'model': model[0], | |
168 | + 'counter': counter[0], | |
169 | + 'status': status[0], | |
170 | + 'network_ip': self.DestHost | |
171 | + } | |
172 | + | |
173 | + log.debug(return_dict) | |
174 | + | |
175 | + return return_dict | |
176 | + | |
64 | 177 | |
65 | 178 | class NmapSession(object): |
66 | 179 | """ |
... | ... | @@ -103,7 +216,7 @@ class NmapSession(object): |
103 | 216 | "nmap", |
104 | 217 | "-PE", |
105 | 218 | "-PP", |
106 | - "-PS21,22,23,25,80,443,3306,3389,8080", | |
219 | + "-PS21,22,23,25,80,443,631,3306,3389,8080,9100", | |
107 | 220 | "-O", |
108 | 221 | str(self.host), |
109 | 222 | "-oX", | ... | ... |
cocar/tests/__init__.py
... | ... | @@ -4,16 +4,25 @@ __author__ = 'eduardo' |
4 | 4 | from .. import Cocar |
5 | 5 | import os |
6 | 6 | import os.path |
7 | +import logging | |
7 | 8 | from ..model import Base |
8 | 9 | |
9 | 10 | cocar = Cocar(environment='test') |
10 | 11 | test_dir = os.path.dirname(os.path.realpath(__file__)) |
12 | +log = logging.getLogger() | |
11 | 13 | |
12 | 14 | |
13 | 15 | def setup_package(): |
14 | 16 | """ |
15 | 17 | Setup test data for the package |
16 | 18 | """ |
19 | + log.debug("Diretório de dados do Cocar: %s", cocar.cocar_data_dir) | |
20 | + test_dir = cocar.cocar_data_dir + "/tests" | |
21 | + if not os.path.isdir(test_dir): | |
22 | + log.info("Criando diretório de testes %s", test_dir) | |
23 | + os.mkdir(test_dir) | |
24 | + | |
25 | + log.info(cocar.engine) | |
17 | 26 | Base.metadata.create_all(cocar.engine) |
18 | 27 | pass |
19 | 28 | ... | ... |
cocar/tests/test_persistence.py
... | ... | @@ -4,10 +4,11 @@ __author__ = 'eduardo' |
4 | 4 | |
5 | 5 | import unittest |
6 | 6 | import cocar.tests |
7 | +import time | |
7 | 8 | from ..xml_utils import NmapXML |
8 | 9 | from..csv_utils import NetworkCSV |
9 | 10 | from ..model.computer import Computer |
10 | -from ..model.printer import Printer | |
11 | +from ..model.printer import Printer, PrinterCounter | |
11 | 12 | from ..model.network import Network |
12 | 13 | |
13 | 14 | |
... | ... | @@ -107,6 +108,82 @@ class TestPersistence(unittest.TestCase): |
107 | 108 | results = self.session.query(Network).first() |
108 | 109 | self.assertIsNotNone(results) |
109 | 110 | |
111 | + def test_printer_counter(self): | |
112 | + """ | |
113 | + Testa inserção do contador em uma impressora | |
114 | + """ | |
115 | + hostname = '10.72.168.4' | |
116 | + ports = { | |
117 | + "9100": { | |
118 | + "state": "open", | |
119 | + "protocol": "tcp", | |
120 | + "service": "vnc-http" | |
121 | + }, | |
122 | + "631": { | |
123 | + "state": "open", | |
124 | + "protocol": "tcp", | |
125 | + "service": "vnc-http" | |
126 | + } | |
127 | + } | |
128 | + | |
129 | + # Agora verifica a inserção do contador | |
130 | + counter = PrinterCounter( | |
131 | + ip_address=hostname, | |
132 | + mac_address=None, | |
133 | + hostname=None, | |
134 | + inclusion_date=time.time(), | |
135 | + open_ports=ports, | |
136 | + scantime='3600', | |
137 | + model='Samsung SCX-6x55X Series', | |
138 | + serial='Z7EUBQBCB03539E', | |
139 | + description='Samsung SCX-6x55X Series; V2.00.03.01 03-23-2012;Engine 0.41.69;NIC V5.01.82(SCX-6x55X) 02-28-2012;S/N Z7EUBQBCB03539E', | |
140 | + counter=1280, | |
141 | + counter_time=time.time() | |
142 | + ) | |
143 | + | |
144 | + self.assertIsInstance(counter, PrinterCounter) | |
145 | + self.assertEqual(counter.counter, 1280) | |
146 | + self.assertEqual(counter.network_ip, hostname) | |
147 | + | |
148 | + self.session.add(counter) | |
149 | + self.session.flush() | |
150 | + # Tenta ver se gravou | |
151 | + results = self.session.query(PrinterCounter).first() | |
152 | + self.assertIsNotNone(results) | |
153 | + | |
154 | + def test_update_counter(self): | |
155 | + """ | |
156 | + Testa inserção dos parâmetros do contador em impressora existente | |
157 | + """ | |
158 | + hostname = '10.72.168.3' | |
159 | + nmap_xml = NmapXML(self.printer_file) | |
160 | + host = nmap_xml.parse_xml() | |
161 | + assert host | |
162 | + | |
163 | + printer = nmap_xml.identify_host(hostname) | |
164 | + self.assertIsInstance(printer, Printer) | |
165 | + | |
166 | + printer_counter = PrinterCounter( | |
167 | + ip_address=printer.ip_address, | |
168 | + mac_address=printer.mac_address, | |
169 | + hostname=printer.hostname, | |
170 | + inclusion_date=printer.inclusion_date, | |
171 | + open_ports=printer.open_ports, | |
172 | + scantime=printer.scantime, | |
173 | + model='Samsung SCX-6x55X Series', | |
174 | + serial='Z7EUBQBCB03539E', | |
175 | + description='Samsung SCX-6x55X Series; V2.00.03.01 03-23-2012;Engine 0.41.69;NIC V5.01.82(SCX-6x55X) 02-28-2012;S/N Z7EUBQBCB03539E', | |
176 | + counter=1280, | |
177 | + counter_time=time.time() | |
178 | + ) | |
179 | + | |
180 | + result = printer_counter.update_counter(self.session) | |
181 | + assert result | |
182 | + | |
183 | + # Aqui não pode inserir de novo | |
184 | + result = printer_counter.update_counter(self.session) | |
185 | + self.assertFalse(result) | |
186 | + | |
110 | 187 | def tearDown(self): |
111 | 188 | """ |
112 | 189 | Remove dados | ... | ... |
cocar/xml_utils.py
... | ... | @@ -50,10 +50,14 @@ class NmapXML(object): |
50 | 50 | ports = element.find('ports') |
51 | 51 | self.hosts[host]['ports'] = dict() |
52 | 52 | for port_xml in ports.findall('port'): |
53 | + if port_xml.find('service') is not None: | |
54 | + service = port_xml.find('service').get('name') | |
55 | + else: | |
56 | + service = None | |
53 | 57 | self.hosts[host]['ports'][port_xml.get('portid')] = { |
54 | 58 | 'protocol': port_xml.get('protocol'), |
55 | 59 | 'state': port_xml.find('state').get('state'), |
56 | - 'service': port_xml.find('service').get('name'), | |
60 | + 'service': service, | |
57 | 61 | } |
58 | 62 | |
59 | 63 | # OS Matches | ... | ... |