Esse enviei 2 apresentações pra Python Brasil. Uma falando sobre python-twitter (e como faço pra enviar os #FF de sexta-feira) e outra pra falar sobre python em telecomunicações.
Não tenho nada escrito ainda, e vou aguardar a confirmação do trabalho pra começar. Se der certo, estarei em Brasília no início de outubro :-)
Para quem tem filho adolescente, sabe que as traquitanas tecnológicas tornaram a vida um inferno quando o assunto é estudar. Controlar o acesso ao computador é fácil, criando regras de bloqueio após certos horários estabelecidos, ou mesmo permissão e filtragem de acessos à sites baseados em URL e squid.
Mas com a chegadas dos dispositivos móveis, como telefones Android e tablets, a coisa não ficou tão fácil.
Restou a opção de bloquear o acesso pelo MAC address nos horários de estudo (que em geral são transformados em horários de soneca durante a tarde ou televisão).
Então fiz um pequeno script em python que bloqueia os MACs já cadastrados, tanto do tablet quanto do smartphone, e libera nos horários em que o computador desktop também está liberado. Com certeza isso não resolve o problema, mas com adolescentes, em geral, quase nada resolve.
O programa funciona com meu roteador wifi, um WR3G01, que comprei no Dealextreme (mais informações aqui). É um roteador wireless que se diz compatível com DD-WRT, mas que pra isso exige soldar os contatos da conexão serial na placa e carregar o firmware por tftp (era mais fácil dizer que não suporta então).
O programa segue:
#! /usr/bin/python
import re, urllib2
from base64 import encodestring
from sys import argv
MACS = [ "94:63:AA:BB:CC:DD", "74:E1:AA:BB:CC:DD" ]
login = "admin"
password = "admin"
server = "192.168.0.1"
port = 80
URI = "/apply.cgi"
TAG =""
for mac in MACS:
mac = re.sub(":",'%3A', mac)
TAG += mac + '%3B'
def Usage():
print "Use: %s {block|allow}" % argv[0]
exit(1)
if (len(argv) < 2):
Usage()
elif (not re.search("allow|block",argv[1])):
Usage()
msg_body = "submit_button=wlan_access&" + \
"change_action=&" + \
"action=Apply&" + \
"AccessControlList0=" + TAG + "&"
if (argv[1] == "allow"):
msg_body += "AccessPolicy0=0&"
elif (argv[1] == "block"):
msg_body += "AccessPolicy0=2&"
msg_body += "mac=&mac=&mac=&mac=&mac=&mac=&"
if (argv[1] == "allow"):
msg_body += "selectMAC=1"
elif (argv[1] == "block"):
msg_body += " selectMAC=3"
http_pass = encodestring(login + ":" + password)
url = "http://" + server + ":" + str(port) + URI
my_headers = {
'User-Agent' : 'Python Client/1.0/1.0',
'Authorization' : 'Basic ' + http_pass,
'Content-Type' : 'application/x-www-form-urlencoded'
}
req = urllib2.Request(
url,
headers = my_headers,
data = msg_body
)
print "Sending: %s" % argv[1]
resp = urllib2.urlopen(req)
Essa é uma apresentação que fiz na iMasters em abril último, no evento 7Masters sobre python.
Não é um curso de python, nem nada próximo disso, mas apenas uma visão de que telecom é na verdade um grande TI, com aplicações que todos nós já conhecemos bem.
Era uma apresentação que deveria levar 7 minutos. Gastei 20.
UPDATE: 2021-12-23 troquei o arquivo embedded de flash pra um gif animado uma vez que nenhum browser sério ainda suporta swf.
Não é de hoje que me deparo com esses erros de unicode em python.
Em algumas funções que uso, principalmente as que lêem de timeline de redes sociais, tenho problemas com caracteres. Dessa vez eu estava fazendo um programa que lê a timeline da rede social da empresa, que é baseada num produto lixo da Microsoft, o ShamePoint, digo, SharePoint, que publica a API de mensagens no formato RSS. A função é pra gerar um gráfico de mensagens a cada 5 minutos para verificar o andamento da rede social (que passou do momento de hype). Então como o ShamePoint é limitado em suas funções, resolvi fazer um "tracking" de posts através de uma função com um hash MD5 da mensagem postada.
A idéia não é muito complexa, mas eis que fazendo o hash eu achei um:
helio@shibboleet:pynet$ ./mynet_tag_posts.py
Getting posts
Reading output and rss
Traceback (most recent call last):
File "./mynet_tag_posts.py", line 106, in
main()
File "./mynet_tag_posts.py", line 103, in main
RSS = RSS_parser(XML)
File "./mynet_tag_posts.py", line 60, in RSS_parser
key = md5sum(rss.summary_detail.value)
File "./mynet_tag_posts.py", line 19, in md5sum
m.update(msg)
UnicodeEncodeError: 'ascii' codec can't encode character u'\xc5' in position 96: ordinal not in range(128)
E a função de hash MD5, a md5sum(), é super simples:
def md5sum(msg):
m = hashlib.md5()
m.update(msg)
return m.hexdigest()
Só que mensagens com caracteres não ASCII, como em português ou suéco, simplesmente matam o processamento do MD5.
Em outros scripts eu contornei isso fazendo uma análise por caractere e substituindo todo aquele que tivesse valor maior que 128 por seu semelhante não acentuado na tabela ASCII. Trabalho tosco, mas funcional.
Dessa vez resolvi procurar como corrigir isso. E pra sempre. Procurei na documentação do python sobre tratamento de unicode.
E não é que achei a solução do jeito mais simples e pythonista possível? Basta forçar um atributo de formato de caractere no texto, como texto.encode(formato). No caso, mudei a função para essa abaixo, usando formato de caracteres "utf-8":
def md5sum(msg):
m = hashlib.md5()
msg = msg.encode('utf-8')
m.update(msg)
return m.hexdigest()
E o problema foi resolvido. Sem mais chororo de formatos iso-8859-1 e utf-8.
Ultimamente, monitorando meu encurtador eri.cx, notei que o mesmo estava recebendo muitos links sem nenhum clique. Olhando mais de perto pude ver que os mesmos eram relacionados à SPAM/vírus. Alguns robôs de Internet, ou mesmo máquinas zumbis, que adicionam esses links suspeitos.
Um dos agressores foi claramente o IP 213.5.66.88. Resolvi o problema adicionando-o em duas regras de firewall bem simples:
iptables -A INPUT -p tcp --dport 80 -s 213.5.66.88 -j LOG
iptables -A INPUT -p tcp --dport 80 -s 213.5.66.88 -j REJECT
Criei uma tabela chamada quarantine e migrei as entradas do banco para lá, manualmente. E também notifiquei os administradores do site sobre o ocorrido, enviando mail para abuse@site e security@site.
Achei que a solução era perfeita e que tinha resolvido meus problemas. Essa semana dei uma olhada novamente na base do encurtador. De 900 links inseridos, uns 350 eram desse tipo (malditos SPAMMERS!).
Chorei minhas mágoas no Twitter, bem no estilo "reclamar no Twitter muito", e recebi algumas sugestões para evitar esses ataques. Mesmo meu guru mental para idéias técno-copiadas, Eduardo Maçan, que administra o encurtador miud.in -- do qual chupinhei várias coisas, sugeriu uma verificação do encurtador via web através do refereer antes de encurtar qualquer link.
Pensei em fazer isso, mas tornaria impossível notificar os administradores dos sites afetados que suas máquinas estão comprometidas, e fora o problema de performance que poderia ocorrer ainda mais que o hosting que contratei tem somente 64 MB de RAM e CPU compartilhada num ambiente Xen. Então resolvi mudar a abordagem e simplesmente deixar as máquinas zumbis adicionarem as URLs "do mal". Mas uma vez por dia eu rodo um programa em Python que faz a busca de padrões desses vírus/scammers, move pra base do quarentine (assim ninguém clicará nos mesmos) mantendo as mesmas entradas, e comunica ao mails abuse@domínio e security@domínio sobre o ataque.
Então segue o script que fiz para esse fim.
#! /usr/bin/python
# -*- coding: utf-8 -*-
# $Id:$
# COPYRIGHT
#
# Copyright (c) 2011 by Helio A. L. Loureiro
#
# Permission to use, copy, modify, distribute, and sell this software
# for any purpose is hereby granted without fee, provided that the above
# copyright notice appear in all copies and that both that copyright
# notice and this permission notice appear in supporting documentation.
# No representations are made about the suitability of this software for
# any purpose. It is provided "as is" without express or implied
# warranty.
### main code
from os import popen
from re import sub, search
import smtplib
from time import ctime,strftime
mysqldb = "eri.cx"
mysqluser = "eri.cx"
mysqlpasswd = "xc.ire"
log_dir = "/var/log"
# Enviando mensagens
def Log(msg):
log_file = log_dir + "/report-sql-spam-" + \
strftime("%Y-%m") + ".log"
msg = msg.rstrip("\n")
msg = "[ " + ctime() + " ] " + msg + "\n"
log = open(log_file, "a")
log.write(msg)
log.close()
# Formatando o texto do mysql
def GenSQLCommand(sql):
cmd = "mysql -u %s -p\'%s\' %s << EOF" % (mysqluser, mysqlpasswd, mysqldb)
cmd = cmd + "\n" + sql + "\n" + "EOF\n"
return cmd
def RetrieveMySQL():
sql = """SELECT
yourls_url.keyword,
yourls_url.url,
yourls_url.title,
yourls_url.timestamp,
yourls_url.ip,
yourls_url.clicks
FROM yourls_url WHERE
yourls_url.title like '%dingo.ucsf.edu%' OR
yourls_url.title like '%cssa.grad.wustl.edu%' OR
yourls_url.title like '%wiki.usfca.edu%' OR
yourls_url.title like '%pantherfile.uwm.edu%' OR
yourls_url.title like '%csci.marietta.edu%' OR
yourls_url.title like '%webfiles.uci.edu%' OR
yourls_url.title like '%Ultram %' OR
yourls_url.title like '%Cialis%' OR
yourls_url.title like '%Tramadol%' OR
yourls_url.title like '%xanax%' OR
yourls_url.title like '%viagra%' ORDER BY yourls_url.keyword;
"""
cmd = GenSQLCommand(sql)
sql_result = popen(cmd).readlines()
sql_result = sql_result[1:]
return sql_result
def InsertQuarantine(db_lines):
sql = """INSERT INTO yourls_quarantine
(keyword, url, title,timestamp,ip,clicks) VALUES """
for e in db_lines:
sql = sql + "\n( "
e = e.rstrip("\n")
for d in e.split("\t"):
sql = sql + "\'" + d + "\', "
sql = sql[:-2] + " ),"
sql = sql[:-1]
cmd = GenSQLCommand(sql)
popen(cmd).read()
def DeleteEntries(keywords):
sql = """DELETE FROM yourls_url USING yourls_url WHERE """
sql = sql + "keyword = \"%s\"" % keywords[0]
for k in range(1, len(keywords)):
sql = sql + "OR keyword = \"%s\"" % keywords[k]
cmd = GenSQLCommand(sql)
popen(cmd).read()
def GetDomain(url):
url = sub("^http://", "", url)
url = sub("^https://", "", url)
url = sub("/.*", "", url)
return url
def SendMail(rcpts, msg):
fromaddr = "helio" + "@" + "domain.com"
mail = smtplib.SMTP('localhost')
mail.sendmail(fromaddr, rcpts, msg)
mail.quit()
def Main():
spam_list = RetrieveMySQL()
ATTACKED = {}
KEYWORDS = []
for spam in spam_list:
Log(sub("\t", ",", spam))
keyword, url, title, timestamp, ip, clicks = spam.split("\t")
domain = GetDomain(url)
KEYWORDS.append(keyword)
try:
ATTACKED[domain].append(spam)
except KeyError:
ATTACKED[domain] = [spam]
for poor_soul in ATTACKED:
rcpts = ["abuse@" + poor_soul,
"root@" + poor_soul,
"security@" + poor_soul]
msg = """From: Helio Loureiro <""" + "helio" + "@" + "domain.com" + """>
To: abuse@%s, root@%s, security@%s
Subject: Service abused from your domain
Hi,
I'm the sysadmin from domain eri.cx, a little poor shortener.
Recently I noticed some attacks (SPAM url shorteneds) coming from
your site.
Here follows the logs:
#timestamp|ip|title|url
""" % (poor_soul, poor_soul, poor_soul)
for spam in ATTACKED[poor_soul]:
keyword, url, title, timestamp, ip, clicks = spam.split("\t")
msg = msg + "%s|%s|%s|%s\n" % (timestamp, ip, title, url)
#print poor_soul, "=>", ATTACKED[poor_soul]
msg = msg + """
It seems your server was compromised. So please take actions accordingly.
This is an automatic mail report. My apologizes if it sent unwanted
information.
Best Regards,
Helio Loureiro
http://eri.cx
"""
#print msg
SendMail(rcpts, msg)
InsertQuarantine(spam_list)
DeleteEntries(KEYWORDS)
if __name__ == '__main__':
Log("== Starting... == ")
Main()
Log("## Finishing... ## ")