Inviare email con il proprio IP esterno

prima stesura di uno script che controlla l’ IP esterno (WAN) e, se variato, invia una email con i dati.
ho modificato uno script di Robert Dempsey.

#!/usr/bin/env python
# encoding: utf-8

import os
import sys
import json
import smtplib
import urllib2
from email import encoders
from email.mime.base import MIMEBase
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart

COMMASPACE = ', '
SMARTHOST = 'INDIRIZZO SMTP'
PORT = 587
SENDER = 'SENDER LOGIN'
PASSWORD = 'SENDER PASSWORD'
RECIPIENTS = ['INDIRIZZI']
MAIL_SUBJECT = 'SOMETHING'
IFCONFIGME = 'http://ifconfig.me'
IPFILE = '/path/to/some/file'

def main():
    req = urllib2.Request(IFCONFIGME + '/all.json')
    try:
        res = urllib2.urlopen(req)
        data = res.read()
        new_json = json.loads(data)
        json.dumps(new_json)
        if os.path.exists(IPFILE):
            with open(IPFILE, 'r') as f:
                old_json = json.load(f)
    except urllib2.URLError as e:
        print e.reason
        raise
    except urllib2.HTTPError as e:
        print e.code
        print e.read()
        raise
    except:
        print 'Errore'
        raise

    try:
        old_json
    except NameError:
        with open(IPFILE, 'w') as f:
            json.dump(new_json, f)
    else:
        if new_json['ip_addr'] == old_json['ip_addr']:
            print 'IP non cambiato'
            sys.exit()

    # Create the enclosing (outer) message
    outer = MIMEMultipart()
    outer['Subject'] = MAIL_SUBJECT
    outer['To'] = COMMASPACE.join(RECIPIENTS)
    outer['From'] = SENDER
    outer.preamble = 'You will not see this in a MIME-aware mail reader.\n'

    body = MIMEText('Nuovo indirizzo IP: ' + new_json['ip_addr'] \
        + '\n\n\n JSON:\n' + json.dumps(new_json, indent=4))
    outer.attach(body)

    # List of attachments
    #attachments = ['FULL PATH TO ATTACHMENTS HERE']
    attachments = []

    # Add the attachments to the message
    for file in attachments:
        try:
            with open(file, 'rb') as fp:
                msg = MIMEBase('application', "octet-stream")
                msg.set_payload(fp.read())
            encoders.encode_base64(msg)
            msg.add_header('Content-Disposition', 'attachment', filename=os.path.basename(file))
            outer.attach(msg)
        except:
            print("Unable to open one of the attachments. Error: ", sys.exc_info()[0])
            raise

    composed = outer.as_string()

    # Send the email
    try:
        s = smtplib.SMTP(SMARTHOST, PORT)
        s.ehlo()
        #s.starttls()
        #s.ehlo()
        s.login(SENDER, PASSWORD)
        s.sendmail(SENDER, RECIPIENTS, composed)
        s.quit()
        print("Email sent!")
    except:
        print("Unable to send the email. Error: ", sys.exc_info()[0])
        raise

if __name__ == '__main__':
    main()

Bash: Estrarre occorrenze multiple di una regex da una linea di testo

Per estrarre occorrenze multiple di una regex da una riga di testo (ad esempio le URL da un file HTML) bisogna ricorrere ad un artificio.

Infatti il pattern “.*” è greedy, cioè va a prendere la più grande occorrenza che trova, non solamente la prima ! Ad esempio “http.*jpg” non trova il primo file, ma tutta la stringa racchiusa tra il primo “http” trovato e l’ ultimo “jpg” trovato.

L’ artificio è il seguente: spezzare la riga di testo con un carattere univoco, ed usarlo come “segnaposto” per la stringa da estrarre.
Il carattere univoco è il “newline”: \n.

Vediamo i singoli passi:

# aggiungere il carattere univoco all' inizio della stringa
# notare il simbolo &
sed 's/http:/\n&/g'
# poi eliminare tutti i caratteri diversi da \n
# notare il [^\n]* tra http e jpg !!!
# è questo ad aggirare il comportamento greedy !!!
sed 's/[^\n]*\n\(http[^\n]*\.jpg\)[^\n]*/\1\n/g'

# METTENDO TUTTO INSIEME:
cat textstring.txt | sed 's/http:/\n&/g;s/[^\n]*\n\(http[^\n]*\.jpg\)[^\n]*/\1\n/g'

Con python è più semplice: basta usare il “?” per modificare il comportamento del pattern “.*” in non-greedy e poi usare il comando findall per trovare tutte le occorrenze in una lista

import re
# notare il "?" che modifica in non-greedy:
regex=re.compile("http.*?\.jpg")
results=regex.findall(file-string)

Analisi di Twisted Proxy

Flusso dei dati

La classe Proxy non fa altro che ereditare da HTTPChannel ed impostare requestFactory=ProxyRequest:

class Proxy(HTTPChannel):
    requestFactory = ProxyRequest

La classe HTTPChannel è un ricevitore di richieste: in essa viene creata la coda delle richieste, e il callback lineReceived(self, line) viene chiamato ogni volta che viene ricevuta una linea, creando una request se la linea è la “prima ricevuta”, oppure completando una request se è una linea successiva.
Quando tutta la request è stata ricevuta dal client, viene eseguito il metodo allContentReceived(): questo a sua volta chiama il metodo request.requestReceived(command, path, version) della richiesta, il quale vedremo in seguito eseguirà la richiesta.
Quando la richiesta avrà finito, essa chiamerà il metodo requestDone di HTTPChannel: se la connessione del client non è persistente verrà chiusa.

class HTTPChannel(basic.LineReceiver, policies.TimeoutMixin):
    ....
    requestFactory = Request
    ....
    def __init__(self):
        # the request queue
        self.requests = []
        ....
    def lineReceived(self, line):
        if self.__first_line:
        .... # fa qualche pulizia di dati precedenti
            request = self.requestFactory(self, len(self.requests))
            self.requests.append(request)

            self.__first_line = 0
            ....
        elif line == b'':
            ....
            self.allHeadersReceived()
            if self.length == 0:
                self.allContentReceived()
            ....
        elif line[0] in b' \t':
            ....
        else:
            ....
            self.__header = line
    ....
    def allContentReceived(self):
        command = self._command
        path = self._path
        version = self._version
        ....
        req = self.requests[-1] # prende la richiesta e
        req.requestReceived(command, path, version) # chiama il suo metodo
    ....
    def requestDone(self, request):
        """
        Called by first request in queue when it is done.
        """
        if request != self.requests[0]: raise TypeError
        del self.requests[0]
        if self.persistent:
            ....
        else:
            self.transport.loseConnection()

A questo punto bisogna ricordare che Proxy (erede di HTTPChannel) ha la proprietà requestFactory impostata a ProxyRequest.
La classe ProxyRequest eredita da Request e ne implementa il metodo astratto process(), oltre a dichiarare il protocollo da adoperare per eseguire la richiesta:

class ProxyRequest(Request):
    protocols = {'http': ProxyClientFactory}
    ports = {'http': 80}
    ....
    def process(self):
        .... # varie inizializzazioni
        port = self.ports[protocol]
        class_ = self.protocols[protocol]
        s = self.content.read()
        clientFactory = class_(self.method, rest, self.clientproto, headers,
                               s, self)
        self.reactor.connectTCP(host, port, clientFactory)

Nella classe genitrice Request ci sono alcuni metodi adoperati da HTTPChannel:

class Request:
    ....
    def requestReceived(self, command, path, version):
        # inizializzazioni varie
        self.process()
    ....
    def process(self):
        pass
    ....
    def notifyFinish(self):
        self.notifications.append(Deferred())
        return self.notifications[-1]
    def finish(self):
        if self._disconnected:
            raise RuntimeError(
                "Request.finish called on a request after its connection was lost; "
                "use Request.notifyFinish to keep track of this.")
        ....
        self.finished = 1
    def write(self, data):
        if self.finished:
            raise RuntimeError('Request.write called on a request after '
                               'Request.finish was called.')
        if not self.startedWriting: # intanto invia la parte HEAD
            self.startedWriting = 1
            version = self.clientproto
            l = []
            l.append(
                version + b" " +
                intToBytes(self.code) + b" " +
                networkString(self.code_message) + b"\r\n")
            ....
            l.append(b"\r\n")
            self.transport.writeSequence(l)
            ....
           # if this is a "HEAD" request, we shouldn't return any data
            if self.method == b"HEAD":
                self.write = lambda data: None
                return

        self.sentLength = self.sentLength + len(data)
        if data:
            if self.chunked:
                self.transport.writeSequence(toChunk(data))
            else:
                self.transport.write(data)
    ....
    def connectionLost(self, reason):
        """
        There is no longer a connection for this request to respond over.
        Clean up anything which can't be useful anymore.
        """
        self._disconnected = True
        self.channel = None
        if self.content is not None:
            self.content.close()
        for d in self.notifications:
            d.errback(reason)
        self.notifications = []

A questo punto la situazione è la seguente:

  • Proxy(HTTPChannel) riceve la connessione con lineReceived e crea una ProxyRequest(Request), inserendola nella sua coda
  • Quando la request è completa, sempre in lineReceived viene chiamato allContentReceived
  • allContentReceived chiama ProxyRequest.requestReceived
  • requestReceived chiama ProxyRequest.process()
  • ProxyRequest.process() crea un clientFactory e lo connette alla destinazione con self.reactor.connectTCP(host, port, clientFactory)

Si è visto che ProxyRequest.process() istanzia una ProxyClientFactory e la connette al server di destinazione della richiesta, per ricevere la risposta.
La factory esegue la connessione e imposta il transport, poi crea il protocollo ProxyClient che esegue la comunicazione vera e propria con il server.
Quando ProxyClient viene collegato al server, viene chiamato il suo callback connectionMade che invia headers e dati attraverso il transport.
Quando il server risponde, ci sono diversi handlers che ricevono i dati.
In particolare, handleResponseEnd() esegue la “pulizia” finale chiamando ProxyRequest.finish() e chiudendo la connessione con il server.
Attenzione: handleResponseEnd viene chiamato da HTTPClient.connectionLost().

class ProxyClient(HTTPClient):
    _finished = False
    ....
    def connectionMade(self):
        self.sendCommand(self.command, self.rest)
        for header, value in self.headers.items():
            self.sendHeader(header, value)
        self.endHeaders()
        self.transport.write(self.data)
    def handleHeader(self, key, value):
    .... # si può riusare per processare ogni header ricevuto
    def handleResponsePart(self, buffer):
        self.father.write(buffer) # usa il metodo write di Request
    def handleResponseEnd(self):
        if not self._finished:
            self._finished = True
            self.father.finish() # chiama il metodo finish di Request
            self.transport.loseConnection() #chiude la connessione

Gestione delle connessioni

Dal lato browser troviamo Proxy e ProxyRequest.
Proxy eredita da Protocol: il metodo connectionLost() è chiamato quando il browser si disconnette dal proxy, e notifica l’ evento a tutte le ProxyRequests ancora in coda

def connectionLost(self, reason):
    self.setTimeout(None)
    for request in self.requests:
        request.connectionLost(reason)

La ProxyRequest.connectionLost fa le pulizie: imposta _disconnected=True, cancella il puntatore al canale di comunicazione con il client, lancia gli errbacks delle notifiche.

def connectionLost(self, reason):
    self._disconnected = True
    self.channel = None
    if self.content is not None:
        self.content.close()
    for d in self.notifications:
        d.errback(reason)
    self.notifications = []

Dal lato opposto, quello che comunica con il server di destinazione, troviamo ProxyClient, che eredita da HTTPClient.
Se il server chiude la comunicazione, viene eseguito il metodo HTTPClient.connectionLost, che a sua volta chiama handleResponseEnd, implementato da ProxyClient:

def handleResponseEnd(self):
    if not self._finished:
        self._finished = True
        self.father.finish()
        self.transport.loseConnection()

Per controllare questo comportamento, basta imitare ciò che avviene nel codice sorgente:
basta cioè fare un test su self._finished

Due volte handleResponseEnd

ProxyClient riceve i dati e in alcuni casi (non so bene quali) esegue una prima volta handleResponseEnd().
Poi però c’è la disconnessione dal server: HTTPClient.connectionLost() chiama self.handleResponseEnd una seconda volta.

Un proxy basilare con Twisted Python

ho rielaborato questo esempio: http://stackoverflow.com/questions/6491932/need-help-writing-a-twisted-proxy

Nello script definisco alcune classi in un ordine ben preciso, per evitare errori di tipo
NameError: name ‘NomeDellaClasse’ is not defined.

Per capire come si imposta il proxy, iniziamo dalla classe proxy.Proxy:
questa eredita da twisted.web.http.HTTPChannel, e la documentazione spiega che per avviare il reactor bisogna chiamare la factory http.HTTPFactory(), dopodichè segnalare alla factory quale classe derivata da proxy.Proxy usare:

class MyProxy(proxy.Proxy):
    pass

f = http.HTTPFactory()
f.protocol = MyProxy
reactor.listenTCP(8888, f)
try:
	reactor.run()
except KeyboardInterrupt:
	reactor.stop()

Questo script definisce un proxy perfettamente funzionante.
Ma mancano varie funzionalità: queste sono definite nei metodi delle varie classi del modulo proxy.

Per usare le altre classi bisogna ridefinirle (override), a cominciare proprio da Proxy:

Proxy

nel suo codice sorgente si vede che c’è la variabile requestFactory, per specificare quale ProxyRequest usare.

ProxyRequest

Ridefinendo proxy.ProxyRequest abbiamo a disposizione i metodi per manipolare le richieste HTTP.
Dal suo codice sorgente si vedono 2 parametri da specificare, protocols e ports.
Il metodo ProxyRequest.process() crea la richiesta:
come si può vedere, le proprietà protocols e ports vengono usate per creare la clientFactory

class ProxyRequest(Request):
    protocols = {'http': ProxyClientFactory}
    ports = {'http': 80}
    ...
    def process(self):
        parsed = urlparse.urlparse(self.uri)
        protocol = parsed[0]
        host = parsed[1]
        port = self.ports[protocol]
        ...
        class_ = self.protocols[protocol]
        clientFactory = class_(self.method, rest, self.clientproto, headers,
                               s, self)
        self.reactor.connectTCP(host, port, clientFactory)

ProxyClientFactory

Con il protocollo MyProxyClientFactory registrato in MyProxyRequest, si gestisce la parte client del proxy, cioè le risposte http.
Ridefinendo ProxyClientFactory si può specificare una classe figlia di ProxyClient.

ProxyClient

Ridefinendo ProxyClient si ha accesso ai metodi per manipolare le risposte.
Vedere il codice sorgente o la documentazione.

Notare che è necessario richiamare gli handler della classe genitrice !

Esempio

Tutto quanto detto finora si traduce nel seguente script:

""" file myproxy.py """
from twisted.web import http, proxy
from twisted.internet import reactor
from twisted.python import log
import sys

""" Salvo lo stdout prima di avviare il logging di twisted """
schermo = sys.stdout

log.startLogging(open('myproxy.log', 'w'))

class MyProxyClient(proxy.ProxyClient):
	def handleHeader(self, key, value):
		s = "Ricevuto header: " + key
		s+= ", " + value
		schermo.write(s + "\n")
		# richiamare il metodo della classe genitrice
		proxy.ProxyClient.handleHeader(self, key, value)

class MyProxyClientFactory(proxy.ProxyClientFactory):
	protocol = MyProxyClient

class MyProxyRequest(proxy.ProxyRequest):
	protocols = {'http': MyProxyClientFactory}
	ports = {'http': 80}

# non è necessario:
#	def process(self):
#		proxy.ProxyRequest.process(self)

class MyProxy(proxy.Proxy):
	requestFactory = MyProxyRequest


f = http.HTTPFactory()
f.protocol = MyProxy
reactor.listenTCP(8888, f)

try:
	reactor.run()
except KeyboardInterrupt:
	reactor.stop()

Il framework Twisted (Python)

appunti su Twisted tratti da Jp Calderone

Uno script “minimo” basato su Twisted ha bisogno di 3 cose:

  1. una resource: crea i contenuti e li “porta” nel protocollo HTTP
  2. una factory: mette in contatto la porta in ascolto con il protocollo HTTP che serve i contenuti
  3. il reactor: è il controllore di tutto il processo, accetta le connessioni TCP e gestisce l’ input/output dei dati

Quindi la struttura di uno script è la seguente:

resource = SomeResourceClass(params....)
factory = SomeFactoryClass(resource)
reactor.listenTCP(int(port_number), factory)
reactor.run()

Esempio 1: generare un contenuto statico

http://jcalderone.livejournal.com/47954.html

from twisted.web.server import Site # importo la factory che genera un "sito"
from twisted.web.static import File #importo la resource che genera pagine in base al filesystem
from twisted.internet import reactor

resource = File('/tmp')
factory = Site(resource)
reactor.listenTCP(8888, factory)
try:
    reactor.run()
except KeyboardInterrupt:
    reactor.stop()

Esempio 2: generare un contenuto statico con diverse URL

http://jcalderone.livejournal.com/48795.html

from twisted.web.server import Site #la factory
from twisted.web.resource import Resource # una resource "vuota"
from twisted.web.static import File #la resource che genera contenuti
from twisted.internet import reactor

root = Resource()
# le resource che rispondono alle URL foo bar e baz mostrano i contenuti di diverse directory
#vengono "appese" alla risorsa root, che diventa così una struttura ad albero
root.putChild("foo", File("/tmp"))
root.putChild("bar", File("/lost+found"))
root.putChild("baz", File("/opt"))

factory = Site(root)
reactor.listenTCP(8880, factory)
reactor.run()

Esempio 3: generare un contenuto dinamico

http://jcalderone.livejournal.com/48953.html

from twisted.web.server import Site # factory
from twisted.web.resource import Resource #resource
from twisted.internet import reactor

from calendar import calendar # il calendario di python

class YearPage(Resource): # override di una Resource, sarà la figlia di Calendar
    def __init__(self, year):
        Resource.__init__(self)
        self.year = year
    # questo metodo viene chiamato dopo una richiesta GET
    def render_GET(self, request): # qui si genera il contenuto
        return "<html><body><pre>%s</pre></body></html>" % (calendar(self.year),)

class Calendar(Resource): #override di una Resource
  def getChild(self, name, request):
      return YearPage(int(name))

root = Calendar()
factory = Site(root)
reactor.listenTCP(8880, factory)
reactor.run()

Esempio 4: risposte asincrone con Deferred

http://jcalderone.livejournal.com/51109.html

from twisted.internet.task import deferLater # subclass di defer.Deferred
from twisted.web.resource import Resource
from twisted.web.server import NOT_DONE_YET
from twisted.internet import reactor

class DelayedResource(Resource):
    def _delayedRender(self, request): # viene chiamata come callback da render_GET
        request.write("Sorry to keep you waiting.") # crea un contenuto
        request.finish() # lo invia come risposta

    def render_GET(self, request): # risposta a GET
        d = deferLater(reactor, 5, lambda: request) # si crea il deferred
        d.addCallback(self._delayedRender) # gli si assegna una callback
        return NOT_DONE_YET # diciamo a render di non produrre adesso la risposta

resource = DelayedResource()