archives/2009/01 http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/ StyXman's glob twisted-RPC http://grulicueva.homelinux.net/~mdione/glob//posts/twisted-RPC/ http://grulicueva.homelinux.net/~mdione/glob//posts/twisted-RPC/ tags/python tags/twisted Wed, 30 Jul 2008 04:03:15 +0200 2009-01-22T04:24:04Z <p>Otra de las grandes cosas de Twisted es que tiene varias opciones de RPC. De éstas, la que me parece más pythonesca es la que dieron en llamar <a href= "http://twistedmatrix.com/projects/core/documentation/howto/pb-intro.html"> Perspective Broker</a>. Con esta parte del framework sólo tenemos que heredar de un par de clases, usar un par de factories ya listas para usar, y ponerle <code>remote_</code> a los métodos que queremos exportar:</p> <pre> <code>from twisted.spread import pb from twisted.internet import reactor, defer class MyServer (pb.Root): # ... def remote_hello (self, greeting): print "client says %s" % greeting return "why, hello there!" s= MyServer () reactor.listenTCP (port, pb.PBServerFactory (s)) reactor.run () </code> </pre> <p>Éste es un server que exporta un método remoto <code>hello</code>. El cliente no es mucho más difícil:</p> <pre> <code>def answered (answer): print "server said %s" % answer def connected (root): root.callRemote ('hello', 'hi server!').addCallback (answered) factory= pb.PBClientFactory () reactor.connectTCP (name, port, factory) factory.getRootObject ().addCallback (connected) </code> </pre> <p>Lo primero que tenemos que pedir antes de poder hacer nada es el "objeto raíz". Éste es el objeto que nos va a permitir acceder a todos los servicios remotos ofrecidos por el server. Este objecto es el <code>s</code> que creamos en el server y que pasamos como objeto raíz a la <code>pb.PBServerFactory</code>. <code>getRootObject()</code> nos devuelve un <code>Deferred</code>.</p> <p>Una vez obtenido el objeto raíz, podemos empezar a llamarle métodos remotos. Notar que <code>callRemote()</code> recibe el nombre del método remoto como un string, y el resto de los parámetros que pasamos son los parámetros con los que va a ser llamado el método remoto en el server. Como es de esperarse, una llamada a un método remoto no vuelve inmediatamente, sino que nos da también un <code>Deferred</code>.</p> <p>Ahora bien, vemos que los <code>Deferred</code>s son la estrella del framework, y que los usamos en todos lados... ¿Qué pasa cuando un método remoto no puede devolver inmediatamente el resultado? Pues, aunque parezca raro y obvio al mismo tiempo, ¡devuelve un <code>Deferred</code>!:</p> <pre> <code> # ... en MyServer def bye (self): return defer.succeed (True) </code> </pre> <p>Acá estoy usando <code>defer.suceed()</code>, que es una función que devuelve un <code>Deferred</code> con su valor ya disponible. Esto es útil para cuando en algunos casos podemos calcular el valor de la respuesta ya, pero en otros casos los tenemos que ir a buscar en otro lado. También existe <code>defer.fail()</code>.</p> <p>Ahora bien, acá entra un poco la magia de Twisted. Es obvio que este <code>Deferred</code> no es el que es devuelto por <code>callRemote()</code>, porque ése es el de haber llamado a un método remoto, no el que devuelve ese método por no tener la respuesta inmediatamente. Lo que no es tan obvio es que este <code>Deferred</code> nunca viaja por la red. Perspective Broker se da cuenta de que el valor no está disponible aún y por lo tanto no retorna nada por la red. Esto es posible porque del otro lado ya hay un <code>Deferred</code> "esperando". Cuando el resultado esté disponible en el server, podemos llamar al método <code>callback()</code> de la promesa con el valor calculado y recién entonces se manda un mensaje por la red y se resuelve la promesa del lado del cliente:</p> <pre> <code> # ... MyServer mxCalc= relaymanager.MXCalculator () def remote_resolveMX (self, hostname): promise= defer.Deferred () mxCalc.getMX (hostname).addCalback (self.resolvedMX, promise) return promise def resolvedMX (self, mxRecord, promise): if mxRecord is not None: answer= mwRecord.name else: answer= None promise.callback (answer) </code> </pre> <p>Acá estoy usando otro detalle que creo que no había presentado antes: estoy llamando a <code>addCallback()</code> no sólo con un callback, sino que le agrego otro parámetro, una referencia a la promesa que devolví. Cuando <code>getMX()</code> resuelve su valor, el callback es llamado con ese resultado como primer parámetro y los otros parámetros que pasé a <code>addCallback()</code> después. Esto me permite juntar el resiltado con la promesa devuelta anteriormente.</p> <p>Hay varias cosas más en el PB. Hay objetos que se pueden referenciar remotamente, que se pueden copiar (hay dos tipos, pero aún no les agarro la mano) y un par de cosas más. Además, Twisted permite RPC usando XML-RPC (buzzword!) y también acceso a/servido de webservices (buzzword!) a través de SOAP y REST (buzzword, buzzword!).</p> <p>Vamos viendo que Twisted tiene muchas cosas pensadas, y que los conceptos que maneja no son muchos ni muy complicados. Si puedo decir que en la forma en que se van 'partiendo' las soluciones en métodos (estados) no ayuda a la legibilidad posterior del código. Por ejemplo, vean cómo empezamos a tener métodos que representan estados y que son varios para algo que de otra forma nos parecería más sencillo:</p> <pre> <code> # ... MyServer def remote_resolveMX (self, hostname): return mxCalc.getMX (hostname) # ... # ... cliente root= factory.getRootObject () mx= root.callRemote ('resolveMX', 'decode.com.ar') # mandar un mail a pyar... </code> </pre> <p>Tengo algunas ideas de cómo simplificar estas cosas. Básicamente se basa en partir el conjunto de máquinas de estados representado en una sola clase a una clase maestra que coordina todo y varias clasesitas que manipulan las distintas maquinitas de estados. Después les cuento bien.</p> <p><a href="http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/../../../tags/twisted/">twisted</a> <a href="http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/../../../tags/python/">python</a></p> trac-con-mercurial-en-etch http://grulicueva.homelinux.net/~mdione/glob//posts/trac-con-mercurial-en-etch/ http://grulicueva.homelinux.net/~mdione/glob//posts/trac-con-mercurial-en-etch/ tags/mercurial tags/sysadmin tags/trac Fri, 18 Jul 2008 03:09:46 +0200 2009-01-22T04:24:04Z <p>Desde hace como dos semanas que vengo esporádicamente peleando con un problema: Tenía que poner a andar el plugin de Mercurial para Trac. Según <a href= "http://trac.edgewall.org/wiki/TracMercurial">las instrucciones de la página</a>, sólo es cuestión de generar un <code>.egg</code> (<a href="http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/posts/trac-me-tiene-los-eggs-llenos/">¿se acuerdan?</a>), ponerlo en el directorio <code>plugins</code> del environment y ya. Pero corriendo <code>tracd</code> y entrando con un browser me daba:</p> <pre> <code>TracError: Unsupported version control system "hg" </code> </pre> <p>Mi primer sospechoso era el <code>.egg</code>; creía que no lo estaba encontrando. Prendiendo el logging en el <code>trac.ini</code> descubrí que en realidad si lo levantaba. Siguiendo un par de links encontré una sugerencia de correr todos los imports que corre el <code>backend.py</code> del plugin. Finalmente descubrí que la línea:</p> <pre> <code>from mercurial.revlog import LookupError </code> </pre> <p><a href= "http://trac.edgewall.org/ticket/7346#comment:6">fallaba</a>. Se vé que el Mercurial que viene en Etch (0.9.1-1+etch1) no es del todo compatible, a pesar que la página del plugin dice que lo es, inclusive hasta con 0.8. Para "repararlo" simplemente saué las (1) referencias a <code>LookupError</code>.</p> <p><a href="http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/../../../tags/trac/">trac</a> <a href="http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/../../../tags/mercurial/">mercurial</a> <a href="http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/../../../tags/sysadmin/">sysadmin</a></p> twismtpy-entrega-remota http://grulicueva.homelinux.net/~mdione/glob//posts/twismtpy-entrega-remota/ http://grulicueva.homelinux.net/~mdione/glob//posts/twismtpy-entrega-remota/ tags/python tags/twismtpy tags/twisted Thu, 10 Jul 2008 22:40:10 +0200 2009-01-22T04:24:04Z <p>Hoy vamos a ver cómo hacer entrega remota de un mail. Como somos un server que recibió un mail que tiene que ser entregado en otro, hay una serie de pasos que tenemos que hacer. Empecemos con una implementación de <code>t.m.s.IMessage</code> que manda a un smarthost:</p> <pre> <code>class Relay (object): implements (smtp.IMessage) def __init__ (self, router, user): # select smarthost based on src domain self.user= user self.smarthost= 'our.smarthost.com' self.lines= [] self.eom= False self.router= router def lineReceived (self, line): self.lines.append (line) def eomReceived (self): return self.send () def send (self, mxRecord=None): sender= smtp.sendmail (self.smarthost, self.user.orig, [self.user.dest], '\n'.join (self.lines), '') sender.addCallback (self.sendComplete) return sender def sendComplete (self, *data): del self.lines return data def connectionLost (self): del self.lines </code> </pre> <p>Una cosa que no expliqué en el post anterior es el valor devuelto por <code>eomReceived()</code>. En ese caso era <code>return self.mailbox.appendMessage (messageData)</code>; en éste, después de un par de vueltas, es el resultado de <code>smtp.sendmail()</code>. Lo que estamos devolviendo es un <code>Deferred</code>.</p> <p>Los <code>Deferred</code>s son una parte importante de Twisted. Son básicamente una promesa de que en algún momento va a haber un valor disponible para devolver, pero que mientras le vamos dadndo esto como para que tenga. El truco es luego conectar con esa promesa nuestros callbacks llamando a <code>addCallback()</code>. Esos callbacks van a ser llamados cuando el valor esté disponible. También se pueden agregar errbacks, que son callbacks que son llamados cuando la operación que pedimos tuvo un error (típicamente una excepción).</p> <p>Eso es exactamente lo que estamos haciendo en <code>send()</code>. <code>t.m.s.sendmail()</code> nos devuelve un <code>Deferred</code> al que le conectamos nuestro <code>sendComplete()</code> y lo devolvemos. <code>sendComplete()</code> simplemente borra las líneas (aparentemente tarde, ya veremos que nos van a hacer falta) y continúa la cadena de callbacks del deferred; cadena que se va armando de esta forma: cuando se llama a <code>callback()</code> en un deferred, éste llama al primer callback. El resultado de este callback es pasado al siguiente, y así.</p> <p>Esto así como está manda por un smarthost. La diferencia entre mandar todo por un smarthost y mandar directamente es que esta última requiere un paso extra: averiguar a qué máquina debe entregarse el mail. Me refiero al registro MX. Vamos a tener que hacer una consulta de DNS mientras recibimos el mail. Una vez que tengamos ambos vamos a poder hacer la entrega, y finalizar. Veamos cómo nos las arreglamos:</p> <pre> <code>mxCalc= relaymanager.MXCalculator () class Relay (object): implements (smtp.IMessage) def __init__ (self, router, user): # deliver by ourselves self.smarthost= None resolver= self.getSMTPServer (user) resolver.addCallback (self.send).addErrback (self.queue) self.lines= [] self.eom= False self.router= router def getSMTPServer (self, user): return mxCalc.getMX (user.dest.domain) def lineReceived (self, line): self.lines.append (line) def eomReceived (self): self.eom= True if self.smarthost is None: print "WARN: mail received and no smarthost!" else: print "mail finished; sending..." self.send () self.sentSignal= defer.Deferred () return self.sentSignal def send (self, mxRecord=None): if mxRecord is not None: # mxRecord is a dns.*Record instance # mxRecord.name is a dns.Name instance self.smarthost= mxRecord.name.name if self.eom: sender= smtp.sendmail (self.smarthost, self.user.orig, [self.user.dest], '\n'.join (self.lines), config.heloAs) sender.addCallback (self.sendComplete).addErrback (self.queue) def queue (self, error): self.router.queue (error=error, user=self.user, mail=self.lines) self.sentSignal.callback (True) def sendComplete (self, *data): del self.lines print ignore self.sentSignal.callback (True) def connectionLost (self): print "WARN: unfinished mail!" del self.lines self.sentSignal.errback (False) </code> </pre> <p>Acá hay varias cosas. Por un lado tenemos una función que se encarga de pedir el registro MX, la que devuelve un <code>Deferred</code> al que le enganchamos nuestra función de entrega. Al mismo tiempo vamos recibiendo el mail, y cuando termine también intenta hacer la entrega. Ahora, acá el truco es crear un <code>Deferred</code> y devolverlo inmediatamente en <code>eomReceived()</code>. Cuando el mail es enviado finalmente, nuestro callback <code>sendComplete()</code> es llamado, el que a su vez hace un callback de nuestro <code>Deferred</code>. Por último, si tenemos un error de entrega, enconlamos a través de nuestro router el mail para un posterior intento de entrega.</p> <p>Lo que vimos en este post es el manejo de <code>Deferred</code>s, y cómo se los usa para prometer volver a llamar cuando el resultado está disponible. Hasta ahora es el único momento en el que realmente he necesitado manejarlos. Supongo que ya volveré a verlos cuando empieze el duro camino de implementar filtros.</p> <p><a href="http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/../../../tags/twismtpy/">twismtpy</a> <a href="http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/../../../tags/python/">python</a> <a href="http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/../../../tags/twisted/">twisted</a></p> ntp-ntpdate http://grulicueva.homelinux.net/~mdione/glob//posts/ntp-ntpdate/ http://grulicueva.homelinux.net/~mdione/glob//posts/ntp-ntpdate/ tags/debian tags/sysadmin tags/ubuntu Thu, 10 Jul 2008 22:34:23 +0200 2009-01-22T04:24:04Z <p>En la oficina tenemos un server que entrega <code>nfs</code>; también tenemos mucha gente que compila cosas. Estos dos parámetros hacen que tener sincronizadas las horas de las máquinas sea una necesidad. Pra esto se pueden usar los paquetes <code>ntpdate</code> (para on-time-sync) y <code>ntp</code> (para keep-in-sync). Hoy estuve revisando bien como interactúan ambos, sobre todo al momento del booteo. antes una descripción de qué hace cada uno.</p> <p><code>ntp</code> se encarga de mantener la hora de la máquina mas-o-menos sincronizada con la de una fuente externa. Si llega a haber cierta diferencia, se encarga de ir acomodando la hora de a poco, para que el sistema no sufra por cambios bruscos. Un problema que tiene es que si la diferencia con la referencia externa es muy grande, <code>ntp</code> no es capaz de salvar las diferencias y entonces ''no hace nada''. <code>ntpdate</code> se encarga simplemente de setear incondicionalmente la hora local según la hora que consiga de esta referencia externa. El escenario ideal sólo haría uso de <code>ntp</code>, pero en general se podría usar también <code>ntpdate</code> para máquinas con el reloj para-el-carajo, como parece ser el caso de al menos una de nuestra máquinas.</p> <p>El tema es que, por defecto, <code>ntpdate</code> utiliza un archivo de configuración de <code>ntp</code>, pero a su vez corre despúes de éste, en cuya condición falla pues el puerto UDP que usa ya está en uso por <code>ntp</code>. Desinstalando <code>ntp</code> nos deja sin el archivo de configuración, y en realidad tiene sentido quedarnos con él.</p> <p>La solución que encontré es simplemente hacer un symlink al script de <code>ntpdate</code> en <code>/etc/network/if-up.d</code> a un nombre anterior al de <code>ntp</code> (por ejemplo, <code>mntpdate</code>). Voy a ver si en debian/ubuntu me dan pelota con lo de ponerles mejor orden que simplemente el nombre.</p> <p><a href="http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/../../../tags/sysadmin/">sysadmin</a> <a href="http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/../../../tags/ubuntu/">ubuntu</a> <a href="http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/../../../tags/debian/">debian</a></p> attachment http://grulicueva.homelinux.net/~mdione/glob//ikiwiki/pagespec/attachment/ http://grulicueva.homelinux.net/~mdione/glob//ikiwiki/pagespec/attachment/ Wed, 09 Jul 2008 22:56:07 +0200 2009-10-21T17:44:07Z <p>This wiki has attachments <strong>disabled</strong>.</p> <p>If attachments are enabled, the wiki admin can control what types of attachments will be accepted, via the <code>allowed_attachments</code> configuration setting.</p> <p>For example, to limit arbitrary files to 50 kilobytes, but allow larger mp3 files to be uploaded by joey into a specific directory, and check all attachments for viruses, something like this could be used:</p> <pre> <code>virusfree() and ((user(joey) and podcast/*.mp3 and mimetype(audio/mpeg) and maxsize(15mb)) or (!ispage() and maxsize(50kb))) </code> </pre> <p>The regular <a href="http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/../../../ikiwiki/pagespec/">PageSpec</a> syntax is expanded with the following additional tests:</p> <ul> <li> <p>"<code>maxsize(size)</code>" - tests whether the attachment is no larger than the specified size. The size defaults to being in bytes, but "kb", "mb", "gb" etc can be used to specify the units.</p> </li> <li> <p>"<code>minsize(size)</code>" - tests whether the attachment is no smaller than the specified size.</p> </li> <li> <p>"<code>ispage()</code>" - tests whether the attachment will be treated by ikiwiki as a wiki page. (Ie, if it has an extension of ".mdwn", or of any other enabled page format).</p> <p>So, if you don't want to allow wiki pages to be uploaded as attachments, use <code>!ispage()</code> ; if you only want to allow wiki pages to be uploaded as attachments, use <code>ispage()</code>.</p> </li> <li> <p>"<code>mimetype(foo/bar)</code>" - checks the MIME type of the attachment. You can include a glob in the type, for example <code>mimetype(image/*)</code>.</p> </li> <li> <p>"<code>virusfree()</code>" - checks the attachment with an antiviral program.</p> </li> </ul> twismtpy-basico http://grulicueva.homelinux.net/~mdione/glob//posts/twismtpy-basico/ http://grulicueva.homelinux.net/~mdione/glob//posts/twismtpy-basico/ tags/python tags/twismtpy tags/twisted Tue, 08 Jul 2008 01:28:27 +0200 2009-01-22T04:24:04Z <p>Vamos a empezar con un server básico, que es con lo que empecé yo. El código es prácticamente lo mismo que está en el libro que mencioné. Básicamnete es un servidor que sabe recibir mails y guardarlo en maildirs:</p> <pre> <code>from twisted.mail import smtp, maildir from twisted.internet import protocol, reactor from zope.interface import implements import os from email.Header import Header class MailDir (object): """ handles the local delivery to a maildir inbox """ implements (smtp.IMessage) def __init__ (self, user): userDir= str (user.dest.local) # we create a directory for this user if not os.path.exists (userDir): os.mkdir (userDir) inboxDir= os.path.join (userDir, 'Inbox') self.mailbox= maildir.MaildirMailbox (inboxDir) self.lines= [] def lineReceived (self, line): self.lines.append (line) def eomReceived (self): # message is complete, store it self.lines.append ('') messageData= '\n'.join (self.lines) return self.mailbox.appendMessage (messageData) def connectionLost (self): # unexpected loss of connectio, don't save del (self.lines) class MailRouter (object): implements (smtp.IMessageDelivery) def __init__ (self, validDomains): self.validDomains= validDomains def receivedHeader (self, helo, origin, recipients): # client is how the client ident'ed itself # clientIP is the ip of the client side's end # we could do a reverse DNS lookup and check if it's true # also check on RBL's and such client, clientIP= helo recipient= recipients[0] # this must be our CNAME myself= 'localhost' value= """from %s [%s] by %s with ESMTP for %s; %s""" % ( client, clientIP, myself, recipient, smtp.rfc822date () ) return "Received: %s" % Header (value) def validateFrom (self, helo, originAddress): self.client= helo # originAddress is a twisted.mail.smtp.Address # if the from is invalid, we should # raise smtp.SMTPBadSender return originAddress def validateTo (self, user): """ routing is the most complicated part of serving an smtp server we can be run on a laptop that only wants to send mail with possibly many source address we can be run on a server that has a local user database; it con be a smarthost for otehr machines it can be a satellite machine with only a smarthost """ if user.dest.domain in self.validDomains: return lambda: Maildir (user) else: raise smtp.SMTPBadRcpt (user) class SMTPFactory (protocol.ServerFactory): def __init__ (self, validDomains): self.validDomains= validDomains def buildProtocol (self, addr): delivery= MailRouter (self.validDomains) smtpProtocol= smtp.SMTP (delivery) smtpProtocol.factory= self return smtpProtocol if __name__=='__main__': import sys # normal local server domains= sys.argv[1].split (',') reactor.listenTCP (2525, SMTPFactory (domains)) reactor.run () </code> </pre> <p>Tenemos tres clases. La primera es <code>SMTPFactory</code>, la cual es sólo un factory de protocols. Tiene un método, <code>buildProtocol()</code> que tiene que construir un protocolo y devolverlo.</p> <p>La segunda que veremos es la MailDir. Ésta implementa la interfaz <code>t.m.s.IMessage</code>, que se usa para la entrega de un mensaje. En este caso es una entrega local a un maildir, aunque luego implementaremos la entrega remota con esta misma interfaz. La clase tiene que implementar tres métodos:</p> <p><code>lineReceived()</code> es llamado por cada nueva línea del mail que llega. No hace diferencias entre si es parte del cuerpo o del header. Si tuviéramos que hacer algún procesamiento, como toquetear headers o rechazar el mail por tamaño, lo deberíamos hacer en este nivel.</p> <p><code>eomReceived()</code> se llama cuando todo el mail ha sido ya entregado a través de <code>lineReceived()</code>. En este caso escribimos el mail finalmente en un maildir. Fíjense que como <code>t.m.maildir.MaildirMailbox</code> no tiene esta misma interfaz, tenemos que acumular la líneas en una lista y pegarlas todas y dársela de commer a <code>t.m.md.MDMB.append()</code>. Ya me sentaré a verificar si no hay una mejor API para esto.</p> <p>Finalmente, <code>connectionLost()</code> se llama si la conexión se pirde antes de recibir todo el mail.</p> <p>Hasta ahora todo sencillo, sólo un factory y la implementación de una entrega local sin muchas luces (no sabe buscar el maildir de un usuario, sino que asume un directorio propio). Gran parte del meollo del mail está en la tercera clase que veremos, <code>MailRouter</code>.</p> <p>La <code>MailRouter</code> es la que sencargará de definir si el mail es entregable o no. En nuestro caso inicial, no la picadura que me estoy rascando, vamos a permitir relaying libre siempre y sólo localmente a través de la clase <code>MailDir</code>. Antes de ver lo métodos implementados, veamos una conversación típica en SMTP:</p> <pre> <code>&gt;&gt;&gt; 220 mustang.grulicueva.net NO UCE NO UBE NO RELAY PROBES &lt;&lt;&lt; helo gurrumin &gt;&gt;&gt; 250 mustang.grulicueva.net Hello 127.0.0.1, nice to meet you &lt;&lt;&lt; mail from: mdione@except.com.ar &gt;&gt;&gt; 250 Sender address accepted &lt;&lt;&lt; rcpt to: mdione@localhost &gt;&gt;&gt; 250 Recipient address accepted &lt;&lt;&lt; rcpt to: root@localhost &gt;&gt;&gt; 250 Recipient address accepted &lt;&lt;&lt; data &gt;&gt;&gt; 354 Continue &lt;&lt;&lt; Subject: bongs &lt;&lt;&lt; To: mdione@whitehouse.gov &lt;&lt;&lt; &lt;&lt;&lt; &lt;&lt;&lt; This is top secret info. So secret we won't even tell you. Sorry. &lt;&lt;&lt; . &gt;&gt;&gt; 250 Delivery in progress &lt;&lt;&lt; quit &gt;&gt;&gt; 221 See you later </code> </pre> <p>Tenemos marcados con <code>&gt;&gt;&gt;</code> lo que escupe el server y con <code>&lt;&lt;&lt;</code> lo que manda el cliente. Básicamente el protocolo se basa en tres fases: presentación, declaración de entrefa y cuerpo. En la presentación el cliente se identifica con un nombre. Acá también puede autenticarse con un par (usuario, passwd), arrancar encripción y todo ese tipo de cosas relativas a la conexión en si. Luego dice de quién viene y a quiénes va el mail, y finalmente el mail en sí. Notar que en esta parte van también los headers (estamos viendo solo dos, un <code>To</code> y un <code>Subject</code>. Estas dos últimas estapas se pueden repetir una detrás de la otra las veces que se quiera.</p> <p>La clase <code>Mailrouter</code> implementa la interfaz <code>t.m.s.IMessageDelivery</code>, se crea una por cada conexión al puerto en el que estamos escuchando, en la que tenemos que implementar otros tres métodos:</p> <p>El primero es el <code>validateFrom()</code>, el que es llamado por cada <code>mail from</code>. Recibe como parametro una tupla de <code>str</code>. El primer <code>str</code> es el nombre que usó el cliente en el comando <code>helo</code> y el segundo es el IP real. También recibe un <code>t.m.s.Address</code> con el from. Como dicen los comentarios, acá podríamos fijarnos si el nombre y el ip coinciden, o si está en un RBL o cosas parecidas, o si damos relay para el from. En este caso aceptamos todo. Notar que lo devuelto es también un <code>t.m.s.Address</code>. Eventualmente se podría devolver otro, aunque no se me ocurre en qué casos.</p> <p>El más importante, el <code>validateTo()</code>. ¿Porqué digo el más importante? Porque éste es el que se encarga de hacer la decisión de ruteo, es decir, de decidir qué implementación de <code>t.m.s.IMessage</code> se va a encargar del delivery basado tanto en el from como en el to del mail. El parámetro que nos pasan es un <code>t.m.s.User</code>, el cual contiene ambas direciones en sus atributos <code>orig</code> y <code>dest</code> respectivamente. En este caso sólo nos fijamos que el destino esté entre los dominos al que le hacemos relay local, y si no levantamos una <code>t.m.s.SMTPBadRcpt</code>, la que se traduce en un mensaje de no relay al cleinte. Notar que lo que devuelve no es ni una instancia ni siquiera la clase que va a implementar el delivery, sino una función que devuelve una instancia. En el próximo post voy a estar mostrando porqué, y en los subsiguientes posts voy a complicar este método para lograr las políticas de relaying que tengo planeado.</p> <p>Finalmente, <code>receiveHeader()</code> es el más sencillo. Sólo tenemos que devolver un string con un header Received apropiado para este delivery. Es éste nos pasan la misma tupla con dos <code>str</code> que en <code>validateFrom()</code>, la dirección de origen y la lista de instancias de <code>t.m.s.User</code> con los recipientes. Ejemplos de estos headers lo podemos encontrar en cualquier mail:</p> <pre> <code>Received: from [192.168.1.77] (unknown [201.250.21.186]) (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)) (No client certificate requested) by mail.madap.com.ar (Postfix) with ESMTPSA id 978604613 for &lt;mdione@grulic.org.ar&gt;; Mon, 30 Jun 2008 08:16:56 -0300 (ART) Received: by 10.141.105.17 with HTTP; Wed, 2 Jul 2008 13:55:38 -0700 (PDT) Received: from localhost ([127.0.0.1] helo=forster.canonical.com) by forster.canonical.com with esmtp (Exim 4.69 #1 (Debian)) id 1KDHgi-0000eo-Fa for &lt;mdione@grulic.org.ar&gt;; Mon, 30 Jun 2008 12:36:52 +0100 </code> </pre> <p>Bueno, es todo por hoy. Tengan en cuenta que todavía no pretendo que éste sea un tutorial de Twisted, pero si va mostrando cosas que nos vamos a encontrar en muchas de las implementaciones de servicios con este framework.</p> <p><a href="http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/../../../tags/twismtpy/">twismtpy</a> <a href="http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/../../../tags/python/">python</a> <a href="http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/../../../tags/twisted/">twisted</a></p> twismtpy http://grulicueva.homelinux.net/~mdione/glob//tags/twismtpy/ http://grulicueva.homelinux.net/~mdione/glob//tags/twismtpy/ Sat, 05 Jul 2008 09:03:12 +0200 2009-01-22T04:24:04Z <div id="feedlink"><a class="feedbutton" type="application/rss+xml" href="index.rss15">RSS</a> <a class="feedbutton" type= "application/atom+xml" href="index.atom15">Atom</a></div> <div class="inlinepage"> <div class="inlineheader"> <p><span class="header"><a href="http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/../../../posts/twismtpy-entrega-remota/">twismtpy-entrega-remota</a></span></p> </div> <div class="inlinecontent"> <p>Hoy vamos a ver cómo hacer entrega remota de un mail. Como somos un server que recibió un mail que tiene que ser entregado en otro, hay una serie de pasos que tenemos que hacer. Empecemos con una implementación de <code>t.m.s.IMessage</code> que manda a un smarthost:</p> <pre> <code>class Relay (object): implements (smtp.IMessage) def __init__ (self, router, user): # select smarthost based on src domain self.user= user self.smarthost= 'our.smarthost.com' self.lines= [] self.eom= False self.router= router def lineReceived (self, line): self.lines.append (line) def eomReceived (self): return self.send () def send (self, mxRecord=None): sender= smtp.sendmail (self.smarthost, self.user.orig, [self.user.dest], '\n'.join (self.lines), '') sender.addCallback (self.sendComplete) return sender def sendComplete (self, *data): del self.lines return data def connectionLost (self): del self.lines </code> </pre> <p>Una cosa que no expliqué en el post anterior es el valor devuelto por <code>eomReceived()</code>. En ese caso era <code>return self.mailbox.appendMessage (messageData)</code>; en éste, después de un par de vueltas, es el resultado de <code>smtp.sendmail()</code>. Lo que estamos devolviendo es un <code>Deferred</code>.</p> <p>Los <code>Deferred</code>s son una parte importante de Twisted. Son básicamente una promesa de que en algún momento va a haber un valor disponible para devolver, pero que mientras le vamos dadndo esto como para que tenga. El truco es luego conectar con esa promesa nuestros callbacks llamando a <code>addCallback()</code>. Esos callbacks van a ser llamados cuando el valor esté disponible. También se pueden agregar errbacks, que son callbacks que son llamados cuando la operación que pedimos tuvo un error (típicamente una excepción).</p> <p>Eso es exactamente lo que estamos haciendo en <code>send()</code>. <code>t.m.s.sendmail()</code> nos devuelve un <code>Deferred</code> al que le conectamos nuestro <code>sendComplete()</code> y lo devolvemos. <code>sendComplete()</code> simplemente borra las líneas (aparentemente tarde, ya veremos que nos van a hacer falta) y continúa la cadena de callbacks del deferred; cadena que se va armando de esta forma: cuando se llama a <code>callback()</code> en un deferred, éste llama al primer callback. El resultado de este callback es pasado al siguiente, y así.</p> <p>Esto así como está manda por un smarthost. La diferencia entre mandar todo por un smarthost y mandar directamente es que esta última requiere un paso extra: averiguar a qué máquina debe entregarse el mail. Me refiero al registro MX. Vamos a tener que hacer una consulta de DNS mientras recibimos el mail. Una vez que tengamos ambos vamos a poder hacer la entrega, y finalizar. Veamos cómo nos las arreglamos:</p> <pre> <code>mxCalc= relaymanager.MXCalculator () class Relay (object): implements (smtp.IMessage) def __init__ (self, router, user): # deliver by ourselves self.smarthost= None resolver= self.getSMTPServer (user) resolver.addCallback (self.send).addErrback (self.queue) self.lines= [] self.eom= False self.router= router def getSMTPServer (self, user): return mxCalc.getMX (user.dest.domain) def lineReceived (self, line): self.lines.append (line) def eomReceived (self): self.eom= True if self.smarthost is None: print "WARN: mail received and no smarthost!" else: print "mail finished; sending..." self.send () self.sentSignal= defer.Deferred () return self.sentSignal def send (self, mxRecord=None): if mxRecord is not None: # mxRecord is a dns.*Record instance # mxRecord.name is a dns.Name instance self.smarthost= mxRecord.name.name if self.eom: sender= smtp.sendmail (self.smarthost, self.user.orig, [self.user.dest], '\n'.join (self.lines), config.heloAs) sender.addCallback (self.sendComplete).addErrback (self.queue) def queue (self, error): self.router.queue (error=error, user=self.user, mail=self.lines) self.sentSignal.callback (True) def sendComplete (self, *data): del self.lines print ignore self.sentSignal.callback (True) def connectionLost (self): print "WARN: unfinished mail!" del self.lines self.sentSignal.errback (False) </code> </pre> <p>Acá hay varias cosas. Por un lado tenemos una función que se encarga de pedir el registro MX, la que devuelve un <code>Deferred</code> al que le enganchamos nuestra función de entrega. Al mismo tiempo vamos recibiendo el mail, y cuando termine también intenta hacer la entrega. Ahora, acá el truco es crear un <code>Deferred</code> y devolverlo inmediatamente en <code>eomReceived()</code>. Cuando el mail es enviado finalmente, nuestro callback <code>sendComplete()</code> es llamado, el que a su vez hace un callback de nuestro <code>Deferred</code>. Por último, si tenemos un error de entrega, enconlamos a través de nuestro router el mail para un posterior intento de entrega.</p> <p>Lo que vimos en este post es el manejo de <code>Deferred</code>s, y cómo se los usa para prometer volver a llamar cuando el resultado está disponible. Hasta ahora es el único momento en el que realmente he necesitado manejarlos. Supongo que ya volveré a verlos cuando empieze el duro camino de implementar filtros.</p> <p><a href="http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/../../../tags/twismtpy/">twismtpy</a> <a href="http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/../../../tags/python/">python</a> <a href="http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/../../../tags/twisted/">twisted</a></p> </div> <div class="inlinefooter"> <p><span class="pagedate">Posted <span class="date">Thu 10 Jul 2008 10:40:10 PM CEST</span></span></p> <p><span class="tags">Tags:</span></p> <p><span class="tags"><a href="http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/../../../tags/python/" rel= "tag">python</a></span></p> <p><span class="tags"><a href="http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/../../../tags/twismtpy/" rel= "tag">twismtpy</a></span></p> <p><span class="tags"><a href="http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/../../../tags/twisted/" rel= "tag">twisted</a></span></p> </div> </div> <div class="inlinepage"> <div class="inlineheader"> <p><span class="header"><a href="http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/../../../posts/twismtpy-basico/">twismtpy-basico</a></span></p> </div> <div class="inlinecontent"> <p>Vamos a empezar con un server básico, que es con lo que empecé yo. El código es prácticamente lo mismo que está en el libro que mencioné. Básicamnete es un servidor que sabe recibir mails y guardarlo en maildirs:</p> <pre> <code>from twisted.mail import smtp, maildir from twisted.internet import protocol, reactor from zope.interface import implements import os from email.Header import Header class MailDir (object): """ handles the local delivery to a maildir inbox """ implements (smtp.IMessage) def __init__ (self, user): userDir= str (user.dest.local) # we create a directory for this user if not os.path.exists (userDir): os.mkdir (userDir) inboxDir= os.path.join (userDir, 'Inbox') self.mailbox= maildir.MaildirMailbox (inboxDir) self.lines= [] def lineReceived (self, line): self.lines.append (line) def eomReceived (self): # message is complete, store it self.lines.append ('') messageData= '\n'.join (self.lines) return self.mailbox.appendMessage (messageData) def connectionLost (self): # unexpected loss of connectio, don't save del (self.lines) class MailRouter (object): implements (smtp.IMessageDelivery) def __init__ (self, validDomains): self.validDomains= validDomains def receivedHeader (self, helo, origin, recipients): # client is how the client ident'ed itself # clientIP is the ip of the client side's end # we could do a reverse DNS lookup and check if it's true # also check on RBL's and such client, clientIP= helo recipient= recipients[0] # this must be our CNAME myself= 'localhost' value= """from %s [%s] by %s with ESMTP for %s; %s""" % ( client, clientIP, myself, recipient, smtp.rfc822date () ) return "Received: %s" % Header (value) def validateFrom (self, helo, originAddress): self.client= helo # originAddress is a twisted.mail.smtp.Address # if the from is invalid, we should # raise smtp.SMTPBadSender return originAddress def validateTo (self, user): """ routing is the most complicated part of serving an smtp server we can be run on a laptop that only wants to send mail with possibly many source address we can be run on a server that has a local user database; it con be a smarthost for otehr machines it can be a satellite machine with only a smarthost """ if user.dest.domain in self.validDomains: return lambda: Maildir (user) else: raise smtp.SMTPBadRcpt (user) class SMTPFactory (protocol.ServerFactory): def __init__ (self, validDomains): self.validDomains= validDomains def buildProtocol (self, addr): delivery= MailRouter (self.validDomains) smtpProtocol= smtp.SMTP (delivery) smtpProtocol.factory= self return smtpProtocol if __name__=='__main__': import sys # normal local server domains= sys.argv[1].split (',') reactor.listenTCP (2525, SMTPFactory (domains)) reactor.run () </code> </pre> <p>Tenemos tres clases. La primera es <code>SMTPFactory</code>, la cual es sólo un factory de protocols. Tiene un método, <code>buildProtocol()</code> que tiene que construir un protocolo y devolverlo.</p> <p>La segunda que veremos es la MailDir. Ésta implementa la interfaz <code>t.m.s.IMessage</code>, que se usa para la entrega de un mensaje. En este caso es una entrega local a un maildir, aunque luego implementaremos la entrega remota con esta misma interfaz. La clase tiene que implementar tres métodos:</p> <p><code>lineReceived()</code> es llamado por cada nueva línea del mail que llega. No hace diferencias entre si es parte del cuerpo o del header. Si tuviéramos que hacer algún procesamiento, como toquetear headers o rechazar el mail por tamaño, lo deberíamos hacer en este nivel.</p> <p><code>eomReceived()</code> se llama cuando todo el mail ha sido ya entregado a través de <code>lineReceived()</code>. En este caso escribimos el mail finalmente en un maildir. Fíjense que como <code>t.m.maildir.MaildirMailbox</code> no tiene esta misma interfaz, tenemos que acumular la líneas en una lista y pegarlas todas y dársela de commer a <code>t.m.md.MDMB.append()</code>. Ya me sentaré a verificar si no hay una mejor API para esto.</p> <p>Finalmente, <code>connectionLost()</code> se llama si la conexión se pirde antes de recibir todo el mail.</p> <p>Hasta ahora todo sencillo, sólo un factory y la implementación de una entrega local sin muchas luces (no sabe buscar el maildir de un usuario, sino que asume un directorio propio). Gran parte del meollo del mail está en la tercera clase que veremos, <code>MailRouter</code>.</p> <p>La <code>MailRouter</code> es la que sencargará de definir si el mail es entregable o no. En nuestro caso inicial, no la picadura que me estoy rascando, vamos a permitir relaying libre siempre y sólo localmente a través de la clase <code>MailDir</code>. Antes de ver lo métodos implementados, veamos una conversación típica en SMTP:</p> <pre> <code>&gt;&gt;&gt; 220 mustang.grulicueva.net NO UCE NO UBE NO RELAY PROBES &lt;&lt;&lt; helo gurrumin &gt;&gt;&gt; 250 mustang.grulicueva.net Hello 127.0.0.1, nice to meet you &lt;&lt;&lt; mail from: mdione@except.com.ar &gt;&gt;&gt; 250 Sender address accepted &lt;&lt;&lt; rcpt to: mdione@localhost &gt;&gt;&gt; 250 Recipient address accepted &lt;&lt;&lt; rcpt to: root@localhost &gt;&gt;&gt; 250 Recipient address accepted &lt;&lt;&lt; data &gt;&gt;&gt; 354 Continue &lt;&lt;&lt; Subject: bongs &lt;&lt;&lt; To: mdione@whitehouse.gov &lt;&lt;&lt; &lt;&lt;&lt; &lt;&lt;&lt; This is top secret info. So secret we won't even tell you. Sorry. &lt;&lt;&lt; . &gt;&gt;&gt; 250 Delivery in progress &lt;&lt;&lt; quit &gt;&gt;&gt; 221 See you later </code> </pre> <p>Tenemos marcados con <code>&gt;&gt;&gt;</code> lo que escupe el server y con <code>&lt;&lt;&lt;</code> lo que manda el cliente. Básicamente el protocolo se basa en tres fases: presentación, declaración de entrefa y cuerpo. En la presentación el cliente se identifica con un nombre. Acá también puede autenticarse con un par (usuario, passwd), arrancar encripción y todo ese tipo de cosas relativas a la conexión en si. Luego dice de quién viene y a quiénes va el mail, y finalmente el mail en sí. Notar que en esta parte van también los headers (estamos viendo solo dos, un <code>To</code> y un <code>Subject</code>. Estas dos últimas estapas se pueden repetir una detrás de la otra las veces que se quiera.</p> <p>La clase <code>Mailrouter</code> implementa la interfaz <code>t.m.s.IMessageDelivery</code>, se crea una por cada conexión al puerto en el que estamos escuchando, en la que tenemos que implementar otros tres métodos:</p> <p>El primero es el <code>validateFrom()</code>, el que es llamado por cada <code>mail from</code>. Recibe como parametro una tupla de <code>str</code>. El primer <code>str</code> es el nombre que usó el cliente en el comando <code>helo</code> y el segundo es el IP real. También recibe un <code>t.m.s.Address</code> con el from. Como dicen los comentarios, acá podríamos fijarnos si el nombre y el ip coinciden, o si está en un RBL o cosas parecidas, o si damos relay para el from. En este caso aceptamos todo. Notar que lo devuelto es también un <code>t.m.s.Address</code>. Eventualmente se podría devolver otro, aunque no se me ocurre en qué casos.</p> <p>El más importante, el <code>validateTo()</code>. ¿Porqué digo el más importante? Porque éste es el que se encarga de hacer la decisión de ruteo, es decir, de decidir qué implementación de <code>t.m.s.IMessage</code> se va a encargar del delivery basado tanto en el from como en el to del mail. El parámetro que nos pasan es un <code>t.m.s.User</code>, el cual contiene ambas direciones en sus atributos <code>orig</code> y <code>dest</code> respectivamente. En este caso sólo nos fijamos que el destino esté entre los dominos al que le hacemos relay local, y si no levantamos una <code>t.m.s.SMTPBadRcpt</code>, la que se traduce en un mensaje de no relay al cleinte. Notar que lo que devuelve no es ni una instancia ni siquiera la clase que va a implementar el delivery, sino una función que devuelve una instancia. En el próximo post voy a estar mostrando porqué, y en los subsiguientes posts voy a complicar este método para lograr las políticas de relaying que tengo planeado.</p> <p>Finalmente, <code>receiveHeader()</code> es el más sencillo. Sólo tenemos que devolver un string con un header Received apropiado para este delivery. Es éste nos pasan la misma tupla con dos <code>str</code> que en <code>validateFrom()</code>, la dirección de origen y la lista de instancias de <code>t.m.s.User</code> con los recipientes. Ejemplos de estos headers lo podemos encontrar en cualquier mail:</p> <pre> <code>Received: from [192.168.1.77] (unknown [201.250.21.186]) (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)) (No client certificate requested) by mail.madap.com.ar (Postfix) with ESMTPSA id 978604613 for &lt;mdione@grulic.org.ar&gt;; Mon, 30 Jun 2008 08:16:56 -0300 (ART) Received: by 10.141.105.17 with HTTP; Wed, 2 Jul 2008 13:55:38 -0700 (PDT) Received: from localhost ([127.0.0.1] helo=forster.canonical.com) by forster.canonical.com with esmtp (Exim 4.69 #1 (Debian)) id 1KDHgi-0000eo-Fa for &lt;mdione@grulic.org.ar&gt;; Mon, 30 Jun 2008 12:36:52 +0100 </code> </pre> <p>Bueno, es todo por hoy. Tengan en cuenta que todavía no pretendo que éste sea un tutorial de Twisted, pero si va mostrando cosas que nos vamos a encontrar en muchas de las implementaciones de servicios con este framework.</p> <p><a href="http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/../../../tags/twismtpy/">twismtpy</a> <a href="http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/../../../tags/python/">python</a> <a href="http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/../../../tags/twisted/">twisted</a></p> </div> <div class="inlinefooter"> <p><span class="pagedate">Posted <span class="date">Tue 08 Jul 2008 01:28:27 AM CEST</span></span></p> <p><span class="tags">Tags:</span></p> <p><span class="tags"><a href="http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/../../../tags/python/" rel= "tag">python</a></span></p> <p><span class="tags"><a href="http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/../../../tags/twismtpy/" rel= "tag">twismtpy</a></span></p> <p><span class="tags"><a href="http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/../../../tags/twisted/" rel= "tag">twisted</a></span></p> </div> </div> <div class="inlinepage"> <div class="inlineheader"> <p><span class="header"><a href="http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/../../../posts/twismtpy/">twismtpy</a></span></p> </div> <div class="inlinecontent"> <p>Bueno, hoy estoy verbose. Se vé que estoy haciendo cosas de nuevo...</p> <p>La cuestión es que, como dije un par de posts atrás, ando con la idea de hacer un servidor de mail no configurable, sino programable. Es decir, que en vez de andar toqueteando archivos de configuración, tratando de adivinar la semántica de cada opción a partir de los manuales, uno se sienta y lo programa de la forma en que uno quiera que se comporte. Esta idea surgió en una charla con otro sysadmin amigo hará unos 3 o 4 años, y quedó dormida hasta que con Lucio vimos la charla de Lighttpd en San Francisco.</p> <p>Es obvio que un proyecto de este estilo no funcionaría bien a menos que la programación sea relativamente sencilla. Y no puede ser sencilla si el lenguaje no es sencillo. Y qué mejor que python para esa tarea. Y si hablamos de python, de servidores y apuntamos un poco alto, no podemos dejar a twisted fuera de la ecuación.</p> <p>Ahora, si hay algo en twisted es su curva de aprendizaje no intuitiva. Twisted es un framework para desarrollo de servidores con un sistema de eventos asíncronos. Con esto se saca un montón de problemas de escalabilidad asociados a servidores con múltiples clientes. El tema es que entonces, se tiene que programar el sistema como una máquina de estados, que en general es así, pero donde cada estado es prácticamente un procedimiento aparte y además no se puede quedar haciendo nada pesado. Esto último es porque twisted es un event loop, y si en uno de los eventos nos quedamos haciendo cosas sin devolver el control al loop, el loop no puede procesar otros eventos.</p> <p>Mas allá de todo eso, me decidí a usarlo lo mismo. <code>twisted.mail</code> tiene un montón de cosas listas para usar, sobre todo muchas interfaces, pero la documentación es inicialmente un poco confusa y no hay un tutorial que uno pueda seguir. Por suerte en la oficina tenemos un "programming with Twisted" que justo viene con ejemplos de un server de SMTP y de un cliente, inclusive explicados.</p> <p>Comenzando con ése es que me largué a hacer esto. Como la idea es que sea programable, y con esto lograr la mayor flexibilidad de configuración, decidí empezar por rascarme donde me pica: me hace falta un server que sepa usar varios samarthosts, dependiendo de a qué red esté conectado y de qué cuenta de mail use para enviar mail, y que soporte encolado cuando no tenga conexión a la red.</p> <p>En sucesivos posts voy a ir poniendo cachos de código mas o menos explicando como funciona todo.</p> <p><a href="http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/../../../tags/python/">python</a> <a href="http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/../../../tags/twisted/">twisted</a> <a href="http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/../../../tags/twismtpy/">twismtpy</a></p> </div> <div class="inlinefooter"> <p><span class="pagedate">Posted <span class="date">Sat 05 Jul 2008 09:02:43 AM CEST</span></span></p> <p><span class="tags">Tags:</span></p> <p><span class="tags"><a href="http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/../../../tags/python/" rel= "tag">python</a></span></p> <p><span class="tags"><a href="http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/../../../tags/twismtpy/" rel= "tag">twismtpy</a></span></p> <p><span class="tags"><a href="http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/../../../tags/twisted/" rel= "tag">twisted</a></span></p> </div> </div> twismtpy http://grulicueva.homelinux.net/~mdione/glob//posts/twismtpy/ http://grulicueva.homelinux.net/~mdione/glob//posts/twismtpy/ tags/python tags/twismtpy tags/twisted Sat, 05 Jul 2008 09:02:43 +0200 2009-01-22T04:24:04Z <p>Bueno, hoy estoy verbose. Se vé que estoy haciendo cosas de nuevo...</p> <p>La cuestión es que, como dije un par de posts atrás, ando con la idea de hacer un servidor de mail no configurable, sino programable. Es decir, que en vez de andar toqueteando archivos de configuración, tratando de adivinar la semántica de cada opción a partir de los manuales, uno se sienta y lo programa de la forma en que uno quiera que se comporte. Esta idea surgió en una charla con otro sysadmin amigo hará unos 3 o 4 años, y quedó dormida hasta que con Lucio vimos la charla de Lighttpd en San Francisco.</p> <p>Es obvio que un proyecto de este estilo no funcionaría bien a menos que la programación sea relativamente sencilla. Y no puede ser sencilla si el lenguaje no es sencillo. Y qué mejor que python para esa tarea. Y si hablamos de python, de servidores y apuntamos un poco alto, no podemos dejar a twisted fuera de la ecuación.</p> <p>Ahora, si hay algo en twisted es su curva de aprendizaje no intuitiva. Twisted es un framework para desarrollo de servidores con un sistema de eventos asíncronos. Con esto se saca un montón de problemas de escalabilidad asociados a servidores con múltiples clientes. El tema es que entonces, se tiene que programar el sistema como una máquina de estados, que en general es así, pero donde cada estado es prácticamente un procedimiento aparte y además no se puede quedar haciendo nada pesado. Esto último es porque twisted es un event loop, y si en uno de los eventos nos quedamos haciendo cosas sin devolver el control al loop, el loop no puede procesar otros eventos.</p> <p>Mas allá de todo eso, me decidí a usarlo lo mismo. <code>twisted.mail</code> tiene un montón de cosas listas para usar, sobre todo muchas interfaces, pero la documentación es inicialmente un poco confusa y no hay un tutorial que uno pueda seguir. Por suerte en la oficina tenemos un "programming with Twisted" que justo viene con ejemplos de un server de SMTP y de un cliente, inclusive explicados.</p> <p>Comenzando con ése es que me largué a hacer esto. Como la idea es que sea programable, y con esto lograr la mayor flexibilidad de configuración, decidí empezar por rascarme donde me pica: me hace falta un server que sepa usar varios samarthosts, dependiendo de a qué red esté conectado y de qué cuenta de mail use para enviar mail, y que soporte encolado cuando no tenga conexión a la red.</p> <p>En sucesivos posts voy a ir poniendo cachos de código mas o menos explicando como funciona todo.</p> <p><a href="http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/../../../tags/python/">python</a> <a href="http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/../../../tags/twisted/">twisted</a> <a href="http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/../../../tags/twismtpy/">twismtpy</a></p> dotproject http://grulicueva.homelinux.net/~mdione/glob//tags/dotproject/ http://grulicueva.homelinux.net/~mdione/glob//tags/dotproject/ Sat, 05 Jul 2008 01:29:16 +0200 2009-01-22T04:24:04Z <div id="feedlink"><a class="feedbutton" type="application/rss+xml" href="index.rss16">RSS</a> <a class="feedbutton" type= "application/atom+xml" href="index.atom16">Atom</a></div> <div class="inlinepage"> <div class="inlineheader"> <p><span class="header"><a href="http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/../../../posts/sarge-etch-2/">sarge-etch-2</a></span></p> </div> <div class="inlinecontent"> <p>El segundo día me amaneció a las 13. Hoy tocaba terminar con el Apache, que incuía apenas los <code>trac</code>s y el <code>dotproject</code>. Ambos implicaban upgrades.</p> <p>Los <code>trac</code> no me hicieron renegar mucho, pues está <a href="http://trac.edgewall.org/wiki/0.10/TracUpgrade">muy bien</a> documentado y hasta fue scripteable. Básicamente era un salto de <a href= "http://trac.edgewall.org/wiki/0.10/TracUpgrade#From0.8.xto0.9"><code> sqlite2</code> a <code>sqlite3</code></a>, un <code>trac-admin ... upgrade</code> seguido de un <code>trac-admin ... resync</code>.</p> <p>Con lo único con lo que renegué fue que a pesar del upgrade fue exitoso no podía entrar. En los logs encontraba esto:</p> <pre> <code>(9)Bad file descriptor: Could not open password file: (null) </code> </pre> <p><a href= "http://idefix.net/~koos/irregular.php/irregular-20070213/mod-authnz-ldap-apache-2-2-and-allowing-all-ldap-users"> Google al rescate</a> me dijo que había que apagar esa directiva que había tenido que modificar el día anterior:</p> <pre> <code>AuthzLDAPAuthoritative off </code> </pre> <p>También me salió esto:</p> <pre> <code>Failed to load the AuthzSVNAccessFile: The character 'o' in rule '@except' is not allowed in authz rules </code> </pre> <p>Eso era porque en un archivo de configuración del repo (<code>conf.svnaccess</code>) tenía los permisos de sólo lectura como <code>ro</code> en vez de <code>r</code>.</p> <p>El <code>dotproject</code> me enfrentó a un viejo archienemigo: <code>mysql</code>. La verdad que no se a queinacarajos se le ocurre que es una excelente idea poner la configuración de acceso y permisos de una base de datos dentro de la base misma. Por un lado eso termina siendo un archivo binario no versionable y por otro obliga al sysadmin a aprender SQL (cosa que sé, pero no manejo fluidamente ni me interesa saberlo; otro de los motivos por los que amo los ORM's). Y además esta configuración termina en <code>/var</code> y no en <code>etc</code>. <code>postgresql</code>, en cambio, es mucho más inteligente. Y viva el SQL independiente del motor. Lástima nadie lo usa...</p> <p>Bien, sólo tuve que hacer un dump del <code>mysql</code> anterior (<code>chroot</code> mediante), crear la base en el nuevo y hacer un load. Fantástico. Luego una lucha trabado con el sistema de permisos antesmencionado. Luego apuntar un browser a <code>https://server/dotproject/install</code>. En ese minisitio tuve primero que configurarlo (como DP no es un paquete en Debian, lo instalé de fuentes; la configuración queda en un archivo en <code>include/config.php</code>; ojo que las otras opciones es nukear las bases), luego volver a entrar a dicha URL, momento en el cual detecta las bases viejas y da la opción de upgradearlas. Anduvo sin problemas y ahora disfrutamos de un DP más nuevo. Yeepee!</p> <p><a href="http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/../../../tags/sysadmin/">sysadmin</a> <a href="http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/../../../tags/debian/">debian</a> <a href="http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/../../../tags/sarge/">sarge</a> <a href="http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/../../../tags/etch/">etch</a> <a href="http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/../../../tags/apache/">apache</a> <a href="http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/../../../tags/trac/">trac</a> <a href="http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/../../../tags/svn/">svn</a> <a href="http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/../../../tags/mysql/">mysql</a> <a href="http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/../../../tags/dotproject/">dotproject</a></p> </div> <div class="inlinefooter"> <p><span class="pagedate">Posted <span class="date">Sat 05 Jul 2008 01:29:16 AM CEST</span></span></p> <p><span class="tags">Tags:</span></p> <p><span class="tags"><a href="http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/../../../tags/apache/" rel= "tag">apache</a></span></p> <p><span class="tags"><a href="http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/../../../tags/debian/" rel= "tag">debian</a></span></p> <p><span class="tags"><a href="http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/../../../tags/dotproject/" rel= "tag">dotproject</a></span></p> <p><span class="tags"><a href="http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/../../../tags/etch/" rel= "tag">etch</a></span></p> <p><span class="tags"><a href="http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/../../../tags/mysql/" rel= "tag">mysql</a></span></p> <p><span class="tags"><a href="http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/../../../tags/sarge/" rel= "tag">sarge</a></span></p> <p><span class="tags"><a href="http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/../../../tags/svn/" rel= "tag">svn</a></span></p> <p><span class="tags"><a href="http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/../../../tags/sysadmin/" rel= "tag">sysadmin</a></span></p> <p><span class="tags"><a href="http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/../../../tags/trac/" rel= "tag">trac</a></span></p> </div> </div> sarge-etch-3 http://grulicueva.homelinux.net/~mdione/glob//posts/sarge-etch-3/ http://grulicueva.homelinux.net/~mdione/glob//posts/sarge-etch-3/ tags/apache tags/ldap tags/sysadmin Sat, 05 Jul 2008 01:29:16 +0200 2009-01-22T04:24:04Z <p>Hoy llegué a la oficina con la idea de que algunos <code>trac</code>s aún no andaban. Resultó que la auth contra LDAP no estaba lista. Encontré que esta configuración si andaba:</p> <pre> <code> AuthLDAPURL ldap://ldap.except.com.ar/ou=People,dc=except,dc=com,dc=ar Require valid-user </code> </pre> <p>Mientras que esta otra no:</p> <pre> <code> AuthLDAPURL ldap://ldap.except.com.ar/ou=People,dc=except,dc=com,dc=ar AuthLDAPGroupAttribute memberUid AuthLDAPGroupAttributeIsDN off Require group cn=except,ou=Group,dc=except,dc=com,dc=ar </code> </pre> <p>Resulta que ahora tenés que avisarle que <a href= "http://www.linux.com/feature/120050">el grupo lo tiene que buscar en el LDAP</a>:</p> <pre> <code> Require ldap-group cn=except,ou=Group,dc=except,dc=com,dc=ar </code> </pre> <p><a href="http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/../../../tags/sysadmin/">sysadmin</a> <a href="http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/../../../tags/apache/">apache</a> <a href="http://grulicueva.homelinux.net/~mdione/glob//archives/2009/01/../../../tags/ldap/">ldap</a></p>