Ahora que estoy en Barcelona dejo el coche durante largos periodos de tiempo en la calle y no me dejan de preguntar si no tengo miedo a que «me lo roben». Mi coche es del año 1997, a punto de convertirse en un clásico, y si me lo robaran lejos de importarme creo que me ayudarían a decidirme de una vez por todas a cambiarlo. A raíz de estas preguntas se me ocurrió cómo mi coche tan antiguo podía pedirme auxilio en caso de que fuera tomado prestado por algún amigo de lo ajeno.
Desde que tengo carnet de conducir, hace más de diez años, soy cliente de la aseguradora Mapfre y desde el año 2011 me ofrecieron contratar la póliza de seguros para gente «joven» Ycar. Con esta póliza aceptas la instalación «ocultad» de un dispositivo GPS y que a través de GPRS actualiza cada 2.000 metros la posición actual de tu vehículo. Además dispone de un acelerómetro como el de los smartphones para que en caso de accidente se avise a la compañía aseguradora. En caso de aviso la aseguradora te llama por teléfono y si no respondes a la llamada avisan a los servicios de emergencia. El objetivo principal por parte de Mapfre es obtener estadísticas de la conducción de sus clientes y desde el punto de vista de los clientes es la obtención de descuentos en la renovación de la póliza en función del número de Kms recorridos y el cumplimiento de la velocidad máxima permitida. Para cumplir con la Ley Orgánica de Protección de Datos la gestión de esta información se realiza a través de una tercera empresa y Mapfre sólo puede conocer la posición de tu vehículo a menos que tú denuncies su robo. En cambio tú puedes ver la posición en todo momento a través de la aplicación para dispositivos móviles Ycar. En teoría la información que recibe Mapfre está anonimizada.
Con semejante despliegue tecnológico en mi coche de la era analógica se me ocurrió diseñar el siguiente sistema de alarma antirobo.
He desarrollado un programa que se conecta periódicamente (en este caso cada 15 minutos) a la página web de Mapfre con mis credenciales y consulta la posición del coche, si la posición ha variado envía una alerta al teléfono móvil a través de un correo electrónico. ¿Qué ocurre si él que ha movido el coche soy yo? Pues con el bot para telegram que expliqué en la última entrada puedo activar o desactivar la alarma desde mi teléfono móvil. Así que cuando estoy por Barcelona intentándome ganara la vida activo la alarma y cuando vuelvo la desactivo.
Ahora detallo la parte técnica (cuando la mayoría de la gente deja de leer). El programa que se conecta periódicamente a la web de Mapfre es un script en Python que utiliza la librería Scrapy para crawlear la web (araña web). Aquí el código fuente:
# -*- coding: utf-8 -*- from scrapy.contrib.spiders.init import InitSpider from scrapy.http import Request, FormRequest from scrapy.contrib.linkextractors.sgml import SgmlLinkExtractor from scrapy.contrib.spiders import Rule import smtplib import sys import time with open("/Users/Documents/YCarLog.txt", "w") as f: f.write("[+] YCarWeb ejecutado a las " + time.ctime()) with open('/Users/Documents/EstadoAlarma.txt') as f: content = f.readlines() print '[+] Estado de la alarma: ' + str(content[0]) if content[0] == "OFF": print '[+] Salir' sys.exit(0) class ycar(InitSpider): name = 'ycar' allowed_domains = ['mapfre.es'] login_page = 'https://www.mapfre.es/oim/ValidarIdentificacionAction.do' start_urls = ['https://www.mapfre.es/oim/DetalleProductoMutuaActionTR.do?destino=subhome&numContratoActual=micontrato&entidad=01'] def init_request(self): """This function is called before crawling starts.""" return Request(url=self.login_page, callback=self.login) def login(self, response): """Generate a login request.""" return FormRequest.from_response(response, formdata={'txtUsuario': 'miusuario', 'txtClave': 'miclave'}, callback=self.check_login_response) def check_login_response(self, response): """Check the response returned by a login request to see if we are successfully logged in. """ #print '[+] response' + response.body if "MAGALLON" in response.body: self.log("Successfully logged in. Let's start crawling!") # Now the crawling can begin.. return self.initialized() else: self.log("Bad times :(") # Something went wrong, we couldn't log in, so nothing happens. def parse(self, response): print '=====================================================================' if "Por favor, vuelva a intentarlo pasado unos minutos, si el" in response.body: self.log("[+] Erro de la web de Mapfre") else: km = response.xpath('//body[@class="home pestTC logado"]/section[@id="contenido"]/div[@id="contenidoInt"]/div[@class="interior twoCols cl"]/section[@id="principal"]/form[@class="caja"]/article[@class="C70 boxshadow roundcorners"][4]/div[@class="info"]/p/span/a/text()').extract() print '[+] km ' + str(km) print "[+] Comprobar km" fh = open("/Users/Documents/ycarKms.txt","a") fh.write('\n' + str(km)) fh.close() fh = open("/Users/Documents/ycarKms.txt","r") content = fh.readlines() print '[+] content[0]: ' + str(content[0]) print '[+] content[1]: ' + str(content[1]) fh.close() if content[0].rstrip('\n') == content[1].rstrip('\n'): print "[+] No hay alarma" fh = open("/Users/Documents/ycarKms.txt","r") lines = fh.readlines() fh.close() fh = open("/Users/Documents/ycarKms.txt","w") for line in lines: if line!=str(content[0]): fh.write(line) fh.close() else: fh = open("/Users/Documents/ycarKms.txt","r") lines = fh.readlines() fh.close() fh = open("/Users/Documents/ycarKms.txt","w") for line in lines: print '[+] line:' + line print '[+] content[0]:' + str(content[0]) if line!=content[0]: fh.write(line) print '[+] Escribe' + line else: print "[+] Linea encontrada" fh.close() print "[+] Alarma!" fromaddr = 'albertomagallonsabado@gmail.com' toaddrs = 'albertomagallonsabado@gmail.com' subject = 'Alarma antirobo python' text = '¡Alarma! El coche fantástico en movimiento.' msg = 'Subject: %s\n\n%s' % (subject, text) username = 'micorreo@gmail.com' password = 'micontrasena' server = smtplib.SMTP('smtp.gmail.com:587') server.ehlo() server.starttls() server.login(username,password) server.sendmail(fromaddr, toaddrs, msg) server.quit()
El código es totalmente optimizable pero tampoco he querido perder más tiempo en este proyecto.
En este código se tratan varios conceptos que no había tenido ocasión de comentar todavía en el blog. Por un lado esta la utilización de scrapy para crear arañas web y por otro lado está la utilización de rutas Xpath para acceder a recursos web. El código fuente anterior es un buen ejemplo de los dos conceptos.
Esta es la salida del script:
root@kali:~/aranas/Ycar# scrapy crawl ycar /root/aranas/Ycar/Ycar/spiders/ycar.py:1: ScrapyDeprecationWarning: Module `scrapy.contrib.spiders` is deprecated, use `scrapy.spiders` instead from scrapy.contrib.spiders.init import InitSpider /root/aranas/Ycar/Ycar/spiders/ycar.py:1: ScrapyDeprecationWarning: Module `scrapy.contrib.spiders.init` is deprecated, use `scrapy.spiders.init` instead from scrapy.contrib.spiders.init import InitSpider /root/aranas/Ycar/Ycar/spiders/ycar.py:3: ScrapyDeprecationWarning: Module `scrapy.contrib.linkextractors` is deprecated, use `scrapy.linkextractors` instead from scrapy.contrib.linkextractors.sgml import SgmlLinkExtractor /root/aranas/Ycar/Ycar/spiders/ycar.py:3: ScrapyDeprecationWarning: Module `scrapy.contrib.linkextractors.sgml` is deprecated, use `scrapy.linkextractors.sgml` instead from scrapy.contrib.linkextractors.sgml import SgmlLinkExtractor [+] Estado de la alarma: ON 2015-09-12 16:04:43 [scrapy] INFO: Scrapy 1.0.3 started (bot: Ycar) 2015-09-12 16:04:43 [scrapy] INFO: Optional features available: ssl, http11 2015-09-12 16:04:43 [scrapy] INFO: Overridden settings: {'NEWSPIDER_MODULE': 'Ycar.spiders', 'SPIDER_MODULES': ['Ycar.spiders'], 'BOT_NAME': 'Ycar'} 2015-09-12 16:04:47 [scrapy] INFO: Enabled extensions: CloseSpider, TelnetConsole, LogStats, CoreStats, SpiderState 2015-09-12 16:04:49 [scrapy] INFO: Enabled downloader middlewares: HttpAuthMiddleware, DownloadTimeoutMiddleware, UserAgentMiddleware, RetryMiddleware, DefaultHeadersMiddleware, MetaRefreshMiddleware, HttpCompressionMiddleware, RedirectMiddleware, CookiesMiddleware, ChunkedTransferMiddleware, DownloaderStats 2015-09-12 16:04:49 [scrapy] INFO: Enabled spider middlewares: HttpErrorMiddleware, OffsiteMiddleware, RefererMiddleware, UrlLengthMiddleware, DepthMiddleware 2015-09-12 16:04:49 [scrapy] INFO: Enabled item pipelines: 2015-09-12 16:04:49 [scrapy] INFO: Spider opened 2015-09-12 16:04:49 [scrapy] INFO: Crawled 0 pages (at 0 pages/min), scraped 0 items (at 0 items/min) 2015-09-12 16:04:49 [scrapy] DEBUG: Telnet console listening on 127.0.0.1:6023 2015-09-12 16:04:55 [scrapy] DEBUG: Crawled (200) <GET https://www.mapfre.es/oim/ValidarIdentificacionAction.do> (referer: None) 2015-09-12 16:04:55 [scrapy] DEBUG: Redirecting (302) to <GET https://www.mapfre.es/oim/ComprobarIrDirectoAction.do> from <POST https://www.mapfre.es/oim/ValidarUsuarioClaveAction.do> 2015-09-12 16:05:05 [scrapy] DEBUG: Crawled (200) <GET https://www.mapfre.es/oim/ComprobarIrDirectoAction.do> (referer: https://www.mapfre.es/oim/ValidarIdentificacionAction.do) 2015-09-12 16:05:05 [ycar] DEBUG: [+] Auntenticado en Mapfre continua el crawling 2015-09-12 16:05:12 [scrapy] DEBUG: Crawled (200) <GET https://www.mapfre.es/oim/DetalleProductoMutuaActionTR.do?destino=subhome&numContratoActual=NNNNNNNNNN&entidad=01> (referer: https://www.mapfre.es/oim/ComprobarIrDirectoAction.do) ===================================================================== [+] km [u'4.836 Km'] [+] Comprobar km [+] content[0]: [u'4.836 Km'] [+] content[1]: [u'4.836 Km'] [+] No hay alarma 2015-09-12 16:05:12 [scrapy] INFO: Closing spider (finished) 2015-09-12 16:05:12 [scrapy] INFO: Dumping Scrapy stats: {'downloader/request_bytes': 1612, 'downloader/request_count': 4, 'downloader/request_method_count/GET': 3, 'downloader/request_method_count/POST': 1, 'downloader/response_bytes': 68484, 'downloader/response_count': 4, 'downloader/response_status_count/200': 3, 'downloader/response_status_count/302': 1, 'finish_reason': 'finished', 'finish_time': datetime.datetime(2015, 9, 12, 14, 5, 12, 892269), 'log_count/DEBUG': 6, 'log_count/INFO': 7, 'request_depth_max': 2, 'response_received_count': 3, 'scheduler/dequeued': 4, 'scheduler/dequeued/memory': 4, 'scheduler/enqueued': 4, 'scheduler/enqueued/memory': 4, 'start_time': datetime.datetime(2015, 9, 12, 14, 4, 49, 549488)} 2015-09-12 16:05:12 [scrapy] INFO: Spider closed (finished)
Una vez que tenemos el script operativo sólo hay que incluirlo en el programador de tareas para que se ejecute periódicamente. Para ello lo he incluído en mi Raspberry Pi que hace la labor de 24×7.
*/15 * * * * cd /root/aranas/Ycar/ && /usr/local/bin/scrapy crawl ycar >/dev/null 2>&1
El resultado en caso de alerta es un mensaje como el siguiente:
También se podría pedir al bot de Telegram que fuese él quien nos alertase. Las posibilidades son muchas. Mapfre podría dar como servicio a sus clientes un sistema de alertas antirrobo similar, en vez de activar o desactivar la alarma a través de un bot de Telegram se podría integrar con bluetooth para que cuando te montes en el coche se desactive la alarma.
Durante la realización de este proyecto encontré algunas brechas de seguridad en la aplicación para móvil Ycar que trataré de explicar más adelante.