Commit 55a73f12f2d9a0b09e25505f60baf2d9e2ba4019

Authored by Eduardo Santos
Committed by Eduardo Santos
2 parents 6b7ffc65 f6e13b2c
Exists in master

Versão que já é capaz de armazenar as impressoras e seus contadores no banco de dados.

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__ = &#39;eduardo&#39;
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__ = &#39;eduardo&#39;
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
... ...
development.ini-dist
1 1 [cocar]
2 2 data_dir = /srv/cocar-agente/cocar_data
3   -netorks_csv = /srv/cocar-agente/cocar_data/networks.csv
  3 +networks_csv = /srv/cocar-agente/cocar_data/networks.csv
4 4 processes = 4
5 5  
6 6 [sqlalchemy]
... ...