lunes, 28 de junio de 2010

IRC con Python (PyIC)

Si quieres programar algo que use IRC en Python puedes hacerlo tu mismo con sockets, puedes usar una librería y programarlo por eventos con pyirclib , PyIRC ...

A la hora de ver este tema, buscaba algo que se encargase de las cosas que no son interesantes del IRC (PING-PONG's, parseado de mensajes) y que no sea orientado a eventos, pero que siguiese permitiendo hacer cosas "poco comunes", como hacer que un archivo que se nos ofreció por esta red lo descargue directamente un servidor FTP (para eso hace falta algo mas, que la librería que trae python no lo permite), pero...

A falta de algo ya escrito, se programa, y eso es lo que traigo, otra librería de IRC más [ pyic.zip ]

El nombre... PyIRC ya estaba cogido xD, asi que supongo que valdrá PyIC (Python IRC Client)

Por si acaso lo dejo claro, la librería no descarga bien, así que no es recomendable usarlo para eso, ademas esta pensado para ser asíncrono, por ejemplo, no se pide la lista de canales y la propia librería la devuelve, sino que hace la peticion al servidor y es cosa del script capturar la lista (aunque la librería ya parsea los mensajes para hacer esta parte mas fácil), repito, no esta hecho para ser la forma mas sencilla, sino para permitir hacer cosas que otras no permiten.

Ahora explicaré las funciones que trae, pero para verlo mejor, aqui hay un pequeño bot que entra a un canal y saluda a los que llegan


#!/usr/bin/env python
# Ejemplo de cliente IRC con pyIC

from  pyic import *

botName = "HelloBot"

server = "irc.freenode.net"
channel = "#bot_testing"
saludo = "Hola"

irc = irc_client(botName,server)

# Esperar por que acabe de "presentarse" el servidor
print "Esperando a que acabe de presentarse..."

while (True):
    msg = irc.getmsg()
    if ( msg.type == RPL_ENDOFMOTD ): # Fin del mensaje del dia
        break
    elif (msg.type == ERR_NOMOTD): # No hay mensaje del dia
        break

print "Entrando a",channel
irc.join(channel) # Se une al canal

print "Saludando :)}"
irc.notice(channel,saludo+" "+channel) # Saluda al canal

# no usa irc.msg() para evitar que los bot's respondan

# Saluda a todos los que hay en el canal
while (True):
    msg = irc.getmsg()

    if (msg.type.upper()=="JOIN"): # Alguien mas entro al canal
        if (msg.by != botName):

            print "Llego",msg.by
            irc.notice(channel,saludo+" "+msg.by)


No hace gran cosa, pero sirve como "Hola mundo" :)

Nota: Normalmente para hablar por el chat se utilizaria irc.sendmsg(), pero este protocolo definió especificamente un tipo de mensaje para que utilizaran los bots automáticos, a los cuales no se debería responder para evitar que varios robots entren en un bucle entre ellos... algo como esto (por decirlo de alguna forma)

Como se puede ver, todo se hace desde un objeto irc_client, que se inicializa con estos elementos:
  • nick: nick del usuario (elemental... xD)
  • server: servidor IRC
  • port: puerto del servidor (6667 por defecto)
  • username: nombre de usuario ("user" por defecto)
  • username2: segundo nombre del usuario ("user" por defecto)
  • fullname: nombre completo del usuario ("user Name"  by default)
  • serverpasswd: contraseña para el servidor (si la tiene)
  • passwd: contraseña para el nick (si la tiene)

A partir de esto, las funciones disponibles son:
  • sendmsg(to,msg): envía un mensaje "msg" para "to"
  • notice(to,msg): como sendmsg, pero este tipo de mensajes no se debe responder
  • chng_nick(nick): cambia de nick
  • join(channel,passwd=None): se une al canal "channel", usando una contraseña, si es necesaria (contraseña desactivada por defecto)
  • quit(msg = "Client quit"): sale del servidor, con un mensaje "msg"
  • quit_channel(channel): sale del canal "channel"
  • kick(channel,nick,comment=None): echa a un usuario "nick" de un canal "channel", el comentario es opcional
  • invite(nick,channel): invita a un usuario "nick" al canal "channel"
  • get_channels(): pide la lista de canales
  • get_names(channel): pide la lista de nicks en un canal "channel"
  • get_topic(channel): pregunta al servidor el tema del canal "channel"
  • set_topic(channel,topic): cambia el tema del canal "channel"
  • whois(user): pide informacion acerca del usuario "user"
  • whowas(user): pide informacion acerca del usuario "user" que ya no esta en el servidor
  • set_mode(mode): cambia el modo del propio usuario
  • set_chanmode(channel,mode,data = None): cambia al modo "mode" el canal "channel", si el modo requiere informacion adicional, se pasa a traves de "data"

La funcion getmsg() requiere una explicación aparte, pues es la que se utiliza para recuperar las comunicaciones del servidor al cliente, devuelve una variable de tipo irc_msg , con las siguientes propiedades:
  • by: nick del emisor del mensaje
  • sender: informacion del emisor
  • to: a donde se envia (el usuario o un canal)
  • type: tipo de mensaje: "PRIVMSG", "NOTICE", explico después un poco mas que es un poco largo
  • msg: el mensaje
  • multiline: si es un tipo de mensaje multilinea
  • multiline_end: si es el fin de un mensaje multilinea
  • ctcp: si contiene un mensaje CTPC
  • ctcp_msg: mensaje ctcp

Los nombres de los tipos de mensaje son los especificados por el RFC de IRC, ademas de "NOTICE" y "PRIVMSG", hay una lista en  [  http://irchelp.org/irchelp/rfc/chapter6.html] (en inglés), aunque se añadio el "DCC_SEND_OFFER" para identificar las ofertas de envio de archivos

Si el mensaje es una oferta de DCC SEND (msg.type == DCC_SEND_OFFER ), además tiene las siguientes propiedades:
  • ip:  IP de la conexión
  • port: puerto de la conexión
  • file: nombre del archivo
  • turbo: si se usa el modo turbo
  • size: tamaño del archivo (o -1 si no se especifica)

Queda por hacer (hoy no... mañaaaaana ):

IRC sobre SSL
Mucho de DCC: enviar, conexiones inversas,transmisiones cifradas... que funcione :P

[Referencias]
http://www.kvirc.de/docu/doc_dcc_connection.html
http://en.wikipedia.org/wiki/Direct_Client-to-Client
http://irchelp.org/irchelp/rfc/index.html
RFC 1459 (en español, version texto)

miércoles, 23 de junio de 2010

Descargando una lista de webproxys

No ando con mucho tiempo para programar, asi que se me ocurrio hacer un pequeño script en python aprovechando el pycrawler para que descargue una lista de webproxies, el codigo no es gran cosa, pero funciona :)

Nota: la lista se descarga de proxy.org

Este es el codigo (o para descargar [proxylist.py])

#!/usr/bin/env python
import pycrawler
import sys
if (len(sys.argv) > 1):
    f = open(sys.argv[1],'w')
else:
    f = open(raw_input('Se guardara en: '),'w')

print "Descargando lista de proxy's..."
c = pycrawler.crawler()
site = c.crawlsite('proxy.org')

# Se buscan los proxys en la pagina
for e in site.element_list:
    if (e.tag_type == "option") and ('value' in e.property):
        if ("." in e.property['value']):
            print "->",e.property['value']
            f.write(e.property['value']+"\n")

f.close()



Hasta otra

domingo, 20 de junio de 2010

pyCrawler 0.2

Pues hay actualizacion del pyCrawler, los cambios son bastante significativos... y lo que es mas importante, ahora hace algo util! :D

El motivo fue que ultimamente aprovechaba el codigo del primer pyCrawler para los scripts que usasen la web, pero no es que sea un codigo muy pythonico (de hecho es bastante malo y tiene algunos bugs), asi que aproveche para reescribirlo desde cero... no le doy mas vueltas:

- Pasa de ser un script a ser mas bien una libreria
- Pasa de administrar las conexiones a mano (con sockets) a hacerlo con urllib, que es estandar, y lo hace mucho mejor
- Se limpio el codigo para hacerlo mas comprensible

Se puede descargar aqui [pycrawler.zip]

El uso es bastante sencillo (o eso se intento), solo hay que importarlo

from pycrawler import *

y crear un objeto (solo es necesario uno), el unico argumento (opcional) es el de proxies, si no se especifica se usara el del sistema, la forma de definirlo es como los de urllib (que es la libreria que se ocupa de esa parte)

c = crawler()

Despues, solo hay que llamar al la funcion crawlsite, con una url

site = c.crawlsite(url)

la variable que se devuelve tiene una sola propiedad (en posteriores versiones habra mas), element_list, que como su nombre indica es una lista de elementos. Cada uno es un elemento de la web, las propiedades que tienen son

  • tag (booleano), indica si es una etiqueta o si es solo texto
  • tag_type (string), el tipo de etiqueta que es ("body", "head", "a",... )
  • property (diccionario), las propiedades de la etiqueta (si tiene), se accede a ellas a traves de su nombre
  • closing (booleano), si es una etiqueta de cierre
  • closed (booleano), si es una etiqueta que se cierra (como <br> )
  • commented (booleano), si esta comentado

Ademas los enlaces ( tag_type == "a" ), tienen las propiedades:

  • absolute (string), url absoluta a donde enlazan
  • range (string), si es local ("local"), externa ("external") o si es javascript ("javascript")

Eso es todo lo que tiene. Asi por ejemplo, este codigo exploraria un website completo:


#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pycrawler import *
c = crawler()

newlist = []
inicio = raw_input('Introduce la url por la que se comenzara: ')
newlist.append(inicio)

mylist = []

# Mientras queden paginas por explorar
while len(newlist) > 0:

    # La nueva pagina se mueve a la lista de las ya exploradas
    this = newlist.pop(0)
    mylist.append(this)

    print this,"..."

    # Se explora
    site = c.crawlsite(this)

    # Se lee la lista de links locales
    for e in site.element_list:

        if (e.tag_type == "a"):
            if (e.range == "local"):

                # Sino se vio antes, se añade a la lista
                if not(e.absolute in mylist) and not(e.absolute in newlist):

                    newlist.append(e.absolute)

print "Mostrando lista (",len(mylist) ,"):"

print mylist



Hasta otra

Actualizacion: acabo de subir una version con los fallos que encontre corregidos (no cai en los "./" y "../" de las url, mal empezamos xD), el enlace es el mismo, [pycrawler.zip]

martes, 15 de junio de 2010

Trasteando con Gambas

Ando trasteando con Gambas, que (a parte del marisco) como sabreis es un IDE/compilador/lenguaje para programacion "visual" bajo GNU/linux, y que al parecer es bastante similar a Basic, y su nombre ya avisa de que "Gambas Almost Means Basic" (Gambas casi significa Basic)

Asi que para probar, monte un programilla que mostrara las temperaturas del pc

El programilla no tiene mucha cosa (solo lee en /proc/acpi/thermal_zone/*/temperature), asi que no me liare con eso...
De la prueba saque algunas conclusiones sobre Gambas:
  • Es sencillo si el objetivo es hacer un programa que se base mucho en la interfaz grafica
  • Sino, sigue siendo sencillo, pero el codigo no me parece tan limpio como podria ser con otros lenguajes (como Python o C)
  • La documentacion es poca, o eso, o simplemente no supe buscarla... el detalle es que la version actual de Gambas es la 2 y los libros que se encuentran por ahi son para la primera version
Y nada mas... por ultimo dejo el codigo del programilla por si resulta util [ Temp-Gambas.tar.gz ] (exportado con "Crear archivo fuente")

Hasta otra!
[Referencias]
http://www.gambas-es.org
http://gambasdoc.org/help/lang
http://www.youtube.com/watch?v=0aOkiEUn3xE
http://sites.google.com/site/codigogambas/

jueves, 10 de junio de 2010

Fortunes con javascript y perl

¿Quien no conoce las miticas frases que suelen salir cuando se hace login en un sistema? (al menos en Slackware), tambien conocidas como Galletas de la suerte, fortunes, o algo asi...

¿Seria curioso poder usarlas tambien en una pagina web y que cada vez que carge la pagina salga una diferente, no?, pues no es demasiado dificil. Si las instalaste " sudo apt-get install fortune " puedes encontrar varias en /usr/share/games/fortunes/ , el formato que siguen es bastante sencillo, es texto plano, separando una "galleta" de otra con una linea con solo un %, seria algo asi:

Frase nº 1
%
Frase nº 2
%
Frase nº 3
...

Siendo tan sencillo el formato, se pueden aprovechar facilmente los archivos para cualquier cosa, por ejemplo (o para descargar [fort2js.pl] ):


#!/usr/bin/env perl

if ($#ARGV != 1){ # Se comprueba que se especificaron los dos archivos
    print "./fort2js.pl <fortune> <javascript>\n";
    exit(0);
}

$fname = $ARGV[0];
$foutname = $ARGV[1];

print "fort2js: ".$fname." -> ".$foutname."\r\n";

$fname = "<".$fname; # Archivo que se lee
$foutname = ">".$foutname; # Archivo que se escribe
$i = 0; # nº de fortunes
open (F, $fname);
open (O, $foutname);
print O "function get_cookie(v){var cookie = new Array(\""; # Cabecera de la funcion

while ($line = <F>){
    if (substr($line,0,1) eq "%"){ # Si es el fin de una fortune
        print O "\",\""; # Nueva posicion en el array
        $i++; # Una fortune mas
    }
    else{
        $line =~ s/\ \ /\ /g; # Se eliminan los dobles espacion
        $line =~ s/"/\\"/g; # Se escapan las comillas
        $line =~ s/\n/<br\/>/g; # Se cambian los saltos de linea por <br/>
        $line =~ s/\r//g; # Se eliminan los retornos de carro
        print O $line;
    }
}
print O "\");return cookie[v]}"; # Fin de la funcion
print O "function max_cookie(){return ".$i.";}"; # Funcion max_cookie()
close F, O;


Esto sirve para convertir un archivo de fortune's en uno de Javascript que permita usar las "galletas" con dos funciones, get_cookie(i) para recuperar la frase numero i y max_cookie() que servira para saber de cuantas "galletas" podemos hacer uso.
El archivo que resulta de hacer ./fort2js.pl <archivo de galletas> < archivo de salida> se puede usar muy facilmente, solo habria que subirlo a algun lugar, y añadir algo como esto al codigo HTML donde queramos que se muestre:


<div id="cookie">[Cargando...]</div>
<script type="text/javascript" src="http://sitio.donde.esta/archivo/donde/esta"></script>
<script type="text/javascript"><!--//

var rand=Math.floor(Math.random()*max_cookie());
document.getElementById('cookie').innerHTML=get_cookie(rand); //--></script>

Y ya estan listas las galletas de la fortuna :D

Hasta ahora!
[Referencias]
http://www.w3schools.com/js/default.asp
http://en.wikipedia.org/wiki/Fortune_(Unix)

domingo, 6 de junio de 2010

Introduccion a la criptografia, con Python: ElGamal (V -1 )

Y vamos por la quinta parte de Introduccion a la criptografia, se dividira en dos porque ya que hablamos de cifrado asimetrico vale la pena ver un cifrado/descifrado y un algoritmo de firma/comprobacion, el problema es que si bien ElGamal cumple perfectamente su parte en lo primero, no tiene un esquema de firmado con el que comparta la generacion de claves (tambien hay un algoritmo de firma ElGamal, pero no es el mismo, y ademas no se considera del todo seguro), asi que ademas veremos un esquema de solo firma ( DSA ) en la segunda parte.
Sin mas espera...

¿Que es ElGamal?

El procedimiento de cifrado/descifrado ElGamal se refiere a un esquema de cifrado basado en problemas matemáticos de algoritmos discretos. Es un algoritmo de criptografía asimétrica basado en la idea de Diffie-Hellman y que funciona de una forma parecida a este algoritmo discreto.
El algoritmo de ElGamal puede ser utilizado tanto para generar firmas digitales como para cifrar o descifrar.
Fue descrito por Taher Elgamal en 1984 y se usa en software GNU Privacy Guard, versiones recientes de PGP, y otros sistemas criptográficos. Este algoritmo no esta bajo ninguna patente lo que lo hace de uso libre. [wikipedia]

Ademas ElGamal comparte con RSA el uso de operaciones matematicas como el calculo de la exponenciacion modular y la generacion de numeros primos, que ya estan posteadas en la entrada correspondiente asi que ya no las incluire aqui para evitar saturar el post con informacion repetida  (aunque si en el codigo fuente al final y para descargar, que ahi no molesta)

Aviso: como sabreis, esta implementacion (como toda esta serie), no se deberia usar para nada real, no solo porque es mas lento que un i286 (lo dicho, esta en python solo para que sea mas facil de entender), sino porque ademas seguro que los programas y librerias que existen cumplen las funciones que necesarias mejor y con mas seguridad

Generando claves

Para generar las claves se realizan las siguientes operaciones (he intentado mantener el mismo nombre de las variables que la Wikipedia para simplificar)

1. Se genera un numero primo (p), a ser posible con un (p-1) que al factorizarlo se obtengan numeros grandes (aqui simplemente se espera que con un p grande p-1 cumpla su condicion )



def genkeys(bitn):

    p = genprime(bitn)



2. Se obtienen dos numeros aleatorios (g y a), g es el generador, parte de la clave publica, mientras que a es la clave privada, g puede ser cualquier numero (aunque se recomienda que se tomen algunas consideraciones de seguridad), mientras que a es un numero entre 0 y p-2 (aunque por seguridad se recomienda usar un numero de 2 a p-2):



    g = random.randint(2**(bitn-1),2**bitn)#Esto se puede sustituir por una lectura a /dev/random, por ejemplo

    a = random.randint(2,p-2)




3. La ultima variable a generar (a2 , A en la Wikipedia) es el resultado de elevar g a la potencia a, y obtener el resultado modulo p



    a2 = modex(g,a,p)



Asi, la clave publica esta formada por las variables p , g y a2 , mientras que la clave privada es a
Por comodidad, la clave publica se carga en un diccionario:



    public = {'p':p,'g':g,'a2':a2}

    private = a

    return public, private



Cifrado y descifrado

De nuevo, el cifrado y descifrado necesitaran convertir el mensaje en un numero, como las funciones ya estan en el post de RSA, no tiene sentido repetirlas

Cifrando...

1. Para cifrar, se convierte el mensaje en un numero n



def cipher(s, public):
    n = msg2num(s)




2. Despues se obtiene un numero pseudo-aleatorio b entre 2 y p-2 (recordemos que p forma parte de la clave publica)



    b = random.randint(2, (public ['p'] -2) )




3. Se eleva g a b modulo p, para conseguir la variable y1



    y1 = modex(public ['g'], b, public['p'])




4. Lo que falta, y2 se obtiene al elevar a2 a b y multiplicarlo por n , el resultado se obtiene modulo p
... o, para evitar dejarse el procesador en la operacion, se eleva a2 a b modulo p, el resultado se multiplica por n y se hace modulo p ( esto aprovecha que hacer la operacion de exponenciacion modular es mas rapida que una exponenciacion normal )



    y2 = modex(public ['a2'], b, public['p'])
    y2 = ( y2 * n ) % public ['p']



El mensaje cifrado son las variables y1 y y2



    return (y1,y2)



Descifrando...

Para descifrar necesitaremos el mensaje cifrado, la clave privada y la publica (al contrario de RSA, que no necesita la clave publica)

1. Recuperamos el numero que corresponde al texto elevando y1 a p - 1 - a , multiplicando el resultado por y2 y obteniendo todo modulo p
... o con exponenciacion modular haciendo y1 elevado a p - 1 - a modulo p, multiplicado por y2, y con el resultado modulo p



def decipher(n, public, private):

    d = modex(n[0], public['p'] -1 -private, public['p'])
    d = (d * n[1]) % public ['p']



2. Solo queda volver a convertir el numero a texto



    s = num2msg(d)
    return s



Y ya esta :D, el codigo fuente esta aqui [ elgamal.py ]

[Referencias]
http://es.wikipedia.org/wiki/ElGamal
http://en.wikipedia.org/wiki/ElGamal