10 mar. 2012

Emulando la navegación en python con mechanize (parte 1)

En estos días me ha tocado programar una aplicación que permita interactuar de manera automática con otra, para ello utilicé la librería python-mechanize.

Está librería permite la interacción con aplicaciones web para automatizar dicha interacción o cuando se quiere realizar pruebas de estrés a una aplicación se usa multi-mechanize.

La página de mechanize la pueden revisar en el siguiente enlace. Hay una guía en Inglés.

En estos día he estado practicando con django (framework de desarrollo web en python) donde espero pronto poder publicar artículos al respecto.

La aplicación en Django es la de manejo de bookmarks, se tiene un inicio de sesión, una página de bienvenida al usuario registrado, luego se puede listar los bookmarks y cerrar la sesión (aplicación en desarrollo).

Pues todo esos pasos se van a automatizar con python-mechanize.

Para instalar mechanize se puede hacer a lo Debian:

#apt-get install python-mechanize

La siguiente figura muestra la aplicación en django sin mucho adorno.

Al darle clip en sesión o Inicio de Sesión aparece la solicitud de usuario y clave como lo muestra la figura:
Al darle botón derecho sobre la entrada de Usuario selecciona Inspeccionar elemento (esto en google chrome o chromium) y aparece una sección donde se muestra el código del formulario.

De esa forma se puede averiguar los nombres de los elementos del formulario para luego utilizarlo en el código python con mechanize.

Se inicia el interprete de comandos de python:
ecrespo@jewel:~$ python
Python 2.7.2+ (default, Dec  1 2011, 01:55:02)
[GCC 4.6.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> 

Se importa mechanize y el manejo de cookies:
>>> import mechanize
>>> import cookielib


Se crea la instancia browser de mechanize:
>>>br = mechanize.Browser()

Se crea la instancia del cookie:
>>>cj = cookielib.LWPCookieJar()

Se asocia la instancia del cookie con el navegador:
>>>br.set_cookiejar(cj)



Se define que no se maneja robots:
>>>br.set_handle_robots(False)

Se define el tiempo de refrescamiento:
>>>br.set_handle_refresh(mechanize._http.HTTPRefreshProcessor(), max_time=1)

Se define las cabeceras del navegador, en este caso se le está diciendo que el navegador es un firefox desde Linux Debian:
>>>br.addheaders = [('User-agent', 'Mozilla/5.0 (X11; U; Linux i686; es-VE; rv:1.9.0.1)Gecko/2008071615 Debian/6.0 Firefox/9')]

Se abre la aplicación web que se encuentra en localhost y puerto 8050:
>>>r = br.open('http://localhost:8050/')

Se muestra el resultado de la página:
>>>print r.read()

En la siguiente figura se muestra el resultado (el código html de la página) del comando anterior:

Ahora se revisarán los links que maneja la página para darle clip a sesión o Inicio de sesión para ingresar el usuario y clave:

Se crea un ciclo con los enlaces existentes en la página, luego se consulta el texto de cada enlace, si es sesion se abre el enlace pasado el url del mismo, luego finaliza el ciclo.
>>> for link in br.links():
...     if link.text == "sesion":
...         r= br.open("%s" %link.absolute_url)
...         break
...     else:
...         continue

Ya en este momento se encuentra en la página de ingreso de usuario y clave:

>>> print br.geturl()
http://localhost:8050/sesion/

Se puede mostrar el código html de la página de Inicio de sesión con el comando br.response().read():
>>>print br.response().read()

En la figura se muestra el resultado del comando:

Se puede mostrar los campos del formulario con el comando br.forms():
>>> for form in br.forms():
...     print form
...
<POST http://localhost:8050/sesion/ application/x-www-form-urlencoded
  <HiddenControl(csrfmiddlewaretoken=d3c9e6c2e06f5014647a1711ee3d4908) (readonly)>
  <TextControl(username=)>
  <PasswordControl(password=)>
  <SubmitControl(<None>=login) (readonly)>
  <HiddenControl(next=/) (readonly)>>

Se nota que se tiene dos campos de entrada de datos username y password y el botón de login para enviar los datos.

Como se tiene un sólo formulario se usa el parámetro nr=0, en el caso que existan varios formularios en dicha página sigue con los números consecutivos o se le pasa el nombre del formulario si existe name="nombre":
>>>br.select_form(nr=0)

Ahora se le pasa los datos de usuario y clave, luego se le da clip al botón de envío:
>>> br.form['username'] = 'usuario'
>>> br.form['password'] = 'clave'
>>> br.submit()
<response_seek_wrapper at 0xb6f0a06cL whose wrapped object = <closeable_response at 0xb6f0d4ccL whose fp = <socket._fileobject object at 0xb6eff5ac>>>


Al ejecutar response().read() se mostrará la página de bienvenida del usuario que ingreso a la aplicación:
>>>print br.response().read()

En la siguiente figura se muestra el resultado del comando:
Se muestra los enlaces disponibles y se selecciona el de publicar:
>>> for enlace in br.links():
...     if enlace.text == "Publicar":
...         s = br.open("%s" %enlace.absolute_url)
...         break
...     else:
...         continue

Se muestra el url de la página:
>>> print br.geturl()
http://localhost:8050/salvar/

Ahora se ingresará un URL de un sitio, por ejemplo www.python.org:

Primero se despliega el formulario:
>>> for form in br.forms():
...     print form
...
<POST http://localhost:8050/salvar/ application/x-www-form-urlencoded
  <HiddenControl(csrfmiddlewaretoken=d3c9e6c2e06f5014647a1711ee3d4908) (readonly)>
  <TextControl(url=)>
  <TextControl(title=)>
  <TextControl(tags=)>
  <SubmitControl(<None>=save) (readonly)>>

Se tiene que pasar el url, luego el título del url y etiquetas:
>>> br.select_form(nr=0)
>>> br.form['url'] = "www.python.org"
>>> br.form['title'] = "Python"
>>> br.form['tags'] = "Python Programacion"
>>> br.submit()
<response_seek_wrapper at 0xb6efee0cL whose wrapped object = <closeable_response at 0xb6f12aacL whose fp = <socket._fileobject object at 0xb6eff86c>>>


Se muestra el contenido de la página luego de ingresar los datos:
 >>> print br.response().read()

En la siguiente figura se muestra el resultado:

Para finalizar se presenta los enlaces disponibles para luego cerrar la sesión del usuario:
>>> for link in br.links():
...     if  link.text == "Cerrar Sesion":
...         r = br.open("%s" %link.absolute_url)
...         break
...     else:
...         continue

Se muestra el código html de la página resultante luego de dar clip en cerrar sesión y la figura donde aparece dicho código:
>>>print br.response().read()
En el siguiente artículo sobre mechanize se explicará el uso de formularios más complejos que sólo entrar datos.



4 mar. 2012

Calculo de direcciones IP con python

Recuerdo cuando vi la materia de redes de computadoras que a uno le tocaba calcular los segmentos de redes, hacer subnetting, supernetting, NAT, etc y  todos los cáculos se tenían que hacer con lapiz y papel  convirtiendo las direcciones IP en 4  bytes separados por punto, incluso en las clases del postgrado no se permitía el uso de calculadoras IP.

Para python existe una librería que permite realizar los cálculos para definir una red, un segmento de red, o reconocer cuando una IP es de un segmento de red dado.

La librería se llama ipcalc, se puede ver la descripción y un ejemplo en el siguiente enlace.

Para instalarlo desde la paqueteria de python se puede usar pip o easy_install:
#pip install ipcalc
Ó
#easy_install ipcalc

Para el caso de Debian se ejecuta apt-get:
#apt-get install python-ipcalc

Desde la consola se ejecuta python:
ecrespo@jewel:~$ python
Python 2.7.2+ (default, Dec  1 2011, 01:55:02)
[GCC 4.6.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>>

Se importa de ipcalc a IP y Network:
>>> from ipcalc import IP, Network

Se presenta el segmento de IPs de la red 192.168.10.0 con bits en 1 de la mascara 30 (mascara 255.255.255.252):
>>> for x in Network('192.168.10.0/30'): print str(x)
...
192.168.10.0
192.168.10.1
192.168.10.2
192.168.10.3


Como se nota se tiene 4 direcciones IP las cuales la primera (192.168.10.0) es la dirección de sub red y la última (192.168.10.3) es la dirección de broadcast de esa sub red.

Si se cambia los bits de la mascara a 29 aumenta el rango de IPs de dicha sub red de 4 a 8 IPs:
>>> for x in Network('192.168.10.0/29'): print str(x)
...
192.168.10.0
192.168.10.1
192.168.10.2
192.168.10.3
192.168.10.4
192.168.10.5
192.168.10.6
192.168.10.7

También se puede consultar si una IP dada se encuentra en un segmento de red o sub red, como ejemplo se consultará si la IP 192.168.10.8 se encuentra en la sub red 192.168.10.0/29 (obvio que no):
>>> '192.168.10.8' in Network('192.168.10.0/29')
False

Si se prueba con la IP 192.168.10.1 devuelve True la consulta:
>>> '192.168.10.1' in Network('192.168.10.0/29')
True


El último ejemplo es el caso de una red 192.68.10.0/22 ó 192.168.10.0/255.255.252.0, se quiere averiguar la IP inicial y la IP final de dicho segmento de red:
>>> '192.168.9.1' in Network('192.168.10.0/22')
False
>>> '192.168.9.255' in Network('192.168.10.0/22')
False
>>> '192.168.10.0' in Network('192.168.10.0/22')
True
>>> '192.168.10.1' in Network('192.168.10.0/22')
True
>>> '192.168.11.1' in Network('192.168.10.0/22')
True
>>> '192.168.12.1' in Network('192.168.10.0/22')
True
>>> '192.168.13.1' in Network('192.168.10.0/22')
True
>>> '192.168.14.1' in Network('192.168.10.0/22')
False
>>> '192.168.13.255' in Network('192.168.10.0/22')
True

Con este resultado se tiene que la IP inicial es la 192.168.10.0 hasta la IP 192.168.13.255.

Se puede usar también para calcular IPs de IPv6.

3 mar. 2012

Crear llave gpg desde Python

Existen varias herramientas para crear, manipular llaves gpg.

Quienes no conozcan de GPG pueden leer el siguiente tutorial.

El paquete para python que permite manejar  las llaves gpg es python-pyme.
#apt-get install python-pyme python-pyme-doc

La documentación de la librería pyme la encuentran en el siguiente enlace.

En la documentación se tiene una lista de ejemplos, se copia el archivo de genkey.py al home del usuario:
cp /usr/share/doc/python-pyme-doc/examples/genkey.py ~/

Se edita el archivo genkey.py, se modifica el tipo de llave a RSA, se define la longitud de la llave (1024,2048 o 4096), longitud de la subllave (el mismo valor de la lñongitud de la llave), el nombre real, comentario del nombre, correo, frase de la llave, fecha de expiración de la llave.

#!/usr/bin/env python
# $Id: genkey.py,v 1.6 2008/03/08 18:21:08 belyi Exp $
# Copyright (C) 2004 Igor Belyi <belyi@users.sourceforge.net>
# Copyright (C) 2002 John Goerzen <jgoerzen@complete.org>
#
#    This program is free software; you can redistribute it and/or #modify
#    it under the terms of the GNU General Public License as #published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty #of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  #See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public #License
#    along with this program; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  #02111-1307  #USA
 

#Importar pyme
from pyme import core, callbacks

# Initialize our context.
#Inicializa el contexto.
core.check_version(None)
c = core.Context()
#Se define algunas configuraciones
c.set_armor(1)
c.set_progress_cb(callbacks.progress_stdout, None)

# This example from the GPGME manual
#Este es un ejemplo desde el manual gpgme.

parms = """<GnupgKeyParms format="internal">
Key-Type: RSA
Key-Length: 2048
Subkey-Type: ELG-E
Subkey-Length: 2048
Name-Real: Ernesto Nadir Crespo Avila
Name-Comment: seraph1
Name-Email: ecrespo@gmail.com
Passphrase: frasedelallave
Expire-Date: 2020-08-15
</GnupgKeyParms>
"""
#Se genera la llave pasandole los parámetros.
c.op_genkey(parms, None, None)
#Se imprime en patanalla el resultado de la generación.
print c.op_genkey_result().fpr


Para crear la llave se ejecuta el archivo genkey.py, :
$python genkey.py
......
PROGRESS UPDATE: what = primegen, type = 46, current = 0, total = 0
PROGRESS UPDATE: what = primegen, type = 46, current = 0, total = 0
PROGRESS UPDATE: what = primegen, type = 43, current = 0, total = 0
PROGRESS UPDATE: what = primegen, type = 43, current = 0, total = 0
PROGRESS UPDATE: what = primegen, type = 43, current = 0, total = 0
PROGRESS UPDATE: what = primegen, type = 43, current = 0, total = 0
PROGRESS UPDATE: what = primegen, type = 43, current = 0, total = 0
PROGRESS UPDATE: what = primegen, type = 94, current = 0, total = 0
PROGRESS UPDATE: what = primegen, type = 94, current = 0, total = 0
PROGRESS UPDATE: what = primegen, type = 94, current = 0, total = 0
PROGRESS UPDATE: what = primegen, type = 94, current = 0, total = 0
FFF598686F3ADE35C52BF65E4478C8341643F0EB





Al desplegar la lista de llaves se tiene lo siguiente:
$gpg --list-keys
...
pub   2048R/1643F0EB 2012-03-04 [caduca: 2020-08-15]
uid                  Ernesto Nadir Crespo Avila (seraph1) <ecrespo@gmail.com>
sub   2048g/9F8E9C20 2012-03-04 [caduca: 2020-08-15]

Los últimos 8 números hexagecimales es el identificador de la llave gpg. Luego se exporta a un servidor de llaves y se puede compartir la llave pública para firmar o cifrar correos.


AddThis