Um Manifesto!

...

  • Increase font size
  • Default font size
  • Decrease font size

Blog

Namespaces on clojure, and compilation.

E-mail Print PDF

O uso de namespaces em clojure pode ser bem simples, mas pode ser bem complicado. Vou focar no simples :)

O namespace de clojure está próximo da declaração pacote/classe no java, assim o padrão é o mesmo para nomenclatura (apesar de poder variar), dessa forma o arquivo clojure em src/twitter/search.clj teria normalmente o ns (ns = namespace) twitter.search.

A declaração básica de um namespace é a através do macro "ns":

(ns twitter.search)  

Com os arquivos definidos com seus devidos namespace, e o classpath (cp) do java configurado certo (apontando para o src/, por exemplo, você pode chamar outros arquivos clojure pelo seu ns:

(ns twitter.main
(:require twitter.search)
)

Agora você teria o ns twitter.search disponível em seu main, mas a forma mais prática é a seguir:

(ns twitter.main
(:require [twitter.search :as search)
)

Assim o conteúdo do ns twitter.search está no simbolo alias search, dessa forma para chamar a função "search-timeline" do twitter.search você faz:

(search/search-timeline "clojure")  

Outra forma útil no ns é o "use", que traz os simbolos do outro ns para o ns atual, assim:

(ns twitter.main (:use twitter.search))
(search-timeline "clojure")

Agora podemos também compilar nossa aplicação clojure, tendo por exemplo a seguinte estrutura de arquivos:

/app
/app/src
/app/src/twitter
/app/src/twitter/main.clj
/app/src/twitter/search.clj
/app/classes
/app/libs

Considerando que seus .jars do clojure estão no libs, conforme este texto, você deve então passar a lancar o clojure com o seguinte comando (veja o novo classpath):

java -cp ./:./src/:./classes/:libs/clojure.jar:libs/clojure-contrib.jar clojure.main $1  

Agora podemos definir um método main no nosso twitter/main.clj :

(ns twitter.main
(:require [twitter.search :as search]))
(defn -main [word]
(let [tweets (search/search word)]
(println (reduce (fn [t0 t1]
(str t0 "\n"
(get t1 "at") " @" (get t1 "from") " : " (get t1 "text")
)) "Search result" tweets ))
))

E o twitter/search.clj:

(ns twitter.search
(:use clojure.contrib.json.read)
(:require [clojure.contrib.http.agent :as http ])
)

(defn search [word]
(let [json (http/string
(http/http-agent
(str "http://search.twitter.com/search.json?q=" word)) )]
(map
(fn [tweet] {"from" (get tweet "from_user")
"text" (get tweet "text")
"at" (get tweet "created_at")})
(get (read-json json) "results"))
)
)

Como pode-se ver usamos tanto o use como o require, assim como as funções mega-hyper-uteis de map e reduce para excercício.

Mais então para compilar o projeto, lance o REPL executando apenas o "./clj" e mande compilar com:

(compile 'twitter.main)  

Agora suas classes estão na pasta classes, você pode executar a aplicação rodando:

$ java -cp ./libs:./classes:./libs/clojure.jar:./libs/clojure-contrib.jar twitter.main clojure  

Assim vai rodar o -main recebendo o "clojure" como argumento. Crie agora o script de start-up e pronto.

Bonus: uma versão em um script de 30 linhas formatadas de uma busca no twitter com clojure. Basta executar o .clj.

Last Updated on Tuesday, 16 March 2010 03:11
 

Hello World, em Clojure, novamente.

E-mail Print PDF
Já escrevi um pouco sobre clojure aqui, sobre o básico de programação funcional e o básico de clojure, mas resolvi pegar do começo agora, do hello world.

Para preparar o ambiente para o clojure basta pegar as bibliotecas do clojure e do clojure-contrib:

wget http://clojure-contrib.googlecode.com/files/clojure-contrib-1.1.0.zip
wget http://clojure.googlecode.com/files/clojure-1.1.0.zip

Descompacte e pegue os jars clojure.jar e clojure-contrib.jar e coloques numa mesma pasta "libs", agora crie um lançador simples para o clojure, com o nome de "clj" (com permissão de execução, com o seguinte conteúdo:

#!/bin/sh
java -cp .:libs/clojure.jar:libs/clojure-contrib.jar clojure.main $1

Agora basta chamar o "clj" para ter o REPL, ou passar como parâmetro o arquivo clojure à ser executado.

Agora como primeiro programa, o clássico "Hello world". Basta criar um arquivo hello.clj com o seguinte conteúdo:

  (println "Hello World")
(defn fac "Nice factorial impl" [x] ( reduce * (range 2 (inc x))))
(println "Factorial of 4 is" (fac 4))
(def myJson "{\"hello\":\"World\"}")
(use 'clojure.contrib.json.read)
(def obj (read-json myJson))
(println (get obj "hello"))

No exemplo, linha a linha, imprimi o Hello World, criei uma função para calculo de fatorial e imprimi o fatorial de 4, criei uma string no formato do json, importei uma biblioteca de json do clojure-contrib, fiz o parse do json e exebi sua propriedade. Basta executar:

$ ./clj hello.clj  

Simples assim.

Last Updated on Tuesday, 09 March 2010 03:52
 

Instalando e configurando NGINX e PHP5 no Debian Lenny

E-mail Print PDF

História

Vou migrar de servidores para um VPS na Linode, o 360, então primeiro preciso ter certeza que tudo vai funcionar lá, para tanto preparei um máquina virtual com a mesma configuração de software que vou preparar por lá, o objetivo é rodar uma cópia de algumas instalações do Joomla, Wordpress e agora um Drupal.

Como o VPS vai ser bem, digamos, compacto, fiz uma instalação básica do Debian Lenny (netinst) sem nada e parti daí. Segue agora como configurar o stack moderninho 2.0. (Uma solução até de preguiçoso, por usar o Fastcgi ao invés do FPM, mas não quero compilar o PHP agora, deixa para o próximo release).

NGINX

Comçando pelo NGINX, o ideal é pegar o código-fonte e compilar, seguem comandos:

# aptitude install build-essential libssl-dev libpcre3-dev -y
# wget http://nginx.org/download/nginx-0.8.33.tar.gz
# tar -zxvf nginx-0.8.33.tar.gz
# cd nginx-0.8.33
# ./configure --sbin-path=/usr/local/sbin --with-http_ssl_module \
--without-mail_pop3_module --without-mail_imap_module \
--without-mail_smtp_module --with-http_stub_status_module
# make
# make install
# ln -s /usr/local/nginx/conf /etc/nginx
# cd ..
# wget htt://www.manifesto.blog.br/extras/nginx
# mv nginx /etc/init.d/nginx
# chmod +x /etc/init.d/nginx
# update-rc.d nginx defaults

Vamos acertar ainda os logs do nginx, editando o arquivo /etc/logrotate.d/nginx:

/var/log/nginx/*.log {
daily
missingok
rotate 7
compress
delaycompress
notifempty
create 640 root adm
sharedscripts
postrotate
[ ! -f /var/run/nginx.pid ] || kill -USR1 `cat /var/run/nginx.pid`
endscript
}

E ainda a configuração do Nginx:

# mkdir /var/log/nginx
# mkdir /var/www

E o conteúdo do arquivo /etc/nginx/nginx.conf :

user  www-data;
worker_processes 4;
error_log /var/log/nginx/error.log;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
access_log /var/log/nginx/access.log;
sendfile on;
keepalive_timeout 60;
tcp_nodelay on;
gzip on;
gzip_http_version 1.0;
gzip_comp_level 2;
gzip_proxied any;
gzip_min_length 1100;
gzip_buffers 16 8k;
gzip_types text/plain text/css application/x-javascript text/xml \
application/xml application/xml+rss text/javascript \
image/gif image/jpeg image/png;
gzip_disable "MSIE [1-6].(?!.*SV1)";
gzip_vary on;
server {
root /var/www/;
server_name localhost;
listen 80;
location / {
index index.php index.html index.htm;
}
location ~ \.php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME /var/www/$fastcgi_script_name;
fastcgi_param SERVER_NAME $http_host;
fastcgi_ignore_client_abort on;
}
}
}

Pronto, agora o nginx está pronto, basta criar o arquivo /etc/init.d/nginx para controlar o serviço, com o seguinte conteúdo:

! /bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/usr/local
DAEMON=/usr/local/sbin/nginx
NAME=nginx
DESC=nginx
test -x $DAEMON || exit 0
if [ -f /etc/default/nginx ] ; then
. /etc/default/nginx
fi
set -e
case "$1" in
start)
echo -n "Starting $DESC: "
start-stop-daemon --start --quiet --pidfile /var/run/$NAME.pid \
--exec $DAEMON -- $DAEMON_OPTS || true
echo "$NAME."
;;
stop)
echo -n "Stopping $DESC: "
start-stop-daemon --stop --quiet --pidfile /var/run/$NAME.pid \
--exec $DAEMON || true
echo "$NAME."
;;
restart|force-reload)
echo -n "Restarting $DESC: "
start-stop-daemon --stop --quiet --pidfile \
/var/run/$NAME.pid --exec $DAEMON || true
sleep 1
start-stop-daemon --start --quiet --pidfile \
/var/run/$NAME.pid --exec $DAEMON -- $DAEMON_OPTS || true
echo "$NAME."
;;
reload)
echo -n "Reloading $DESC configuration: "
start-stop-daemon --stop --signal HUP --quiet --pidfile /var/run/$NAME.pid \
--exec $DAEMON || true
echo "$NAME."
;;
configtest)
echo -n "Testing $DESC configuration: "
if nginx -t > /dev/null 2>&1
then
echo "$NAME."
else
exit $?
fi
;;
*)
echo "Usage: $NAME {start|stop|restart|reload|force-reload|configtest}" >&2
exit 1
;;
esac
exit 0

E então o ativamos para iniciar automáticamente:

# chmod +x /etc/init.d/nginx
# update-rc.d nginx defaults
# /etc/init.d/nginx start

PHP

Seguimos com a instalação do PHP5, APC e seus principais pacotes, usando os do dotdeb.org para termos versões mais recente, então primeiro adcione o dotdeb aos seus repositórios, adcionando as seguintes linhas ao /etc/apt/sources.list:

deb http://dotdeb.mirror.somersettechsolutions.co.uk/ stable all
deb-src http://dotdeb.mirror.somersettechsolutions.co.uk/ stable all

E agora instalamos o php5 sem o apache, e com os principais modulos:

# aptitude update 
# aptitude install php5 php5-cli php5-cgi php5-curl php5-gd php5-common \
php5-memcache php5-mysql php5-pgsql php5-sqlite php5-apc

Configuramos e agora criamos o script para controlar o serviço do fastcgi do php, em /etc/init.d/php-fastcgi :

#!/bin/bash
BIND=127.0.0.1:9000
USER=www-data
PHP_FCGI_CHILDREN=15
PHP_FCGI_MAX_REQUESTS=1000
PHP_CGI=/usr/bin/php-cgi
PHP_CGI_NAME=`basename $PHP_CGI`
PHP_CGI_ARGS="- USER=$USER PATH=/usr/bin \
PHP_FCGI_CHILDREN=$PHP_FCGI_CHILDREN \
PHP_FCGI_MAX_REQUESTS=$PHP_FCGI_MAX_REQUESTS \
$PHP_CGI -b $BIND"
RETVAL=0
start() {
echo -n "Starting PHP FastCGI: "
start-stop-daemon --quiet --start --background --chuid "$USER" \
--exec /usr/bin/env -- $PHP_CGI_ARGS
RETVAL=$?
echo "$PHP_CGI_NAME."
}
stop() {
echo -n "Stopping PHP FastCGI: "
killall -q -w -u $USER $PHP_CGI
RETVAL=$?
echo "$PHP_CGI_NAME."
}

case "$1" in
start)
start
;;
stop)
stop
;;
restart)
stop
start
;;
*)
echo "Usage: php-fastcgi {start|stop|restart}"
exit 1
;;
esac
exit $RETVAL

E então o ativamos para iniciar automáticamente:

# chmod +x /etc/init.d/php-fastcgi
# update-rc.d php-fastcgi defaults

Agora com o PHP configurado, terminamos criando uma página e acessando o nosso servidor local, crie em /var/www/index.php como "< ? phpinfo() ? >" e acesse o ip do servidor para ver se funcionou... ou não.

Last Updated on Friday, 26 February 2010 02:33
 

Rede de máquinas virtuais com KVM e VDE

E-mail Print PDF

História

Como parte da migração de servidores que estou fazendo, montei uma pequena estrutura virtual para testes... na verdade levantei uma maquina virtual com o KVM com configuração semelhante a que vou usar no VPS.

O objetivo inicial é levantar a máquina com acesso SSH, para poder configurar o servidor (nginx, php e mysql), então segue o passo-a-passo para rede no KVM. O básico do KVM/QEmu pode-se conferir nesse mini tutorial do qemu e nesse micro guia do KVM.

Preparando a VM

Então comece criando o HD, baixando a ISO de sua distro favorita, que é claro que é o Debian, e siga a instalação padrão. Para manter mais parecido com o VPS mantive tudo em uma partição só e deixei um espacinho para o swap.

# qemu-img -f qcow2 debian.qcow 10G
# kvm -monitor stdio -smp 2 -m 360 -localtime -hda debian5.qcow -cdrom debian-lenny.iso
-boot d -net nic,vlan=0 -net user,vlan=0 -name "debian-lenny"

Esses comandos só criam um HD de 10GB e iniciam o KVM com dois processadores virtuais, 360MB de ram, usando as imagens devidas e rede local padrão. Basta seguir a instalação padrão, depois para carregar a VM use o mesmo comando mas sem a imagem do cd e com boot do hda:

# kvm -monitor stdio -smp 2 -m 360 -localtime -hda debian5.qcow -boot c -net nic,vlan=0
-net user,vlan=0 -name "debian-lenny"

Rede do KVM com VDE

Agora parte importante para ter uma rede lega é usar o VDE (virtual distributed ethernet) para configurar a rede do KVM, assim podendo acessar ele como um servidor normal, e ao contrário, assim como em outras VMS.

Primeiro instale o instale o VDE, para a rede distribuída, e o dnsmasq, para servir o DHCP para os guests, no host:

# aptitude install vde2 dnsmasq 
# modprobe tun
# adduser seu_usuario vde2-net
$ newgrp vde2-net

E configure suas interfaces de rede para o vde2, em /etc/network/interfaces adicione:

auto tap0
iface tap0 inet static
address 10.0.2.1
netmask 255.255.255.0
network 10.0.2.0
broadcast 10.0.2.255
pre-up tunctl -u diogo -t tap0
pre-up /etc/init.d/dnsmasq restart
up iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
up echo 1 > /proc/sys/net/ipv4/ip_forward
vde2-switch -

Troque "diogo" pelo seu usuário que vai iniciar a VM, e as faixas de ip se precisar.

Configure por final o DNSMASQ, ao final do /etc/dnsmasq.conf adcione:

user=nobody 
domain=qemu.lan
interface=tap0
dhcp-range=10.0.2.1,10.0.2.253,255.255.255.0,10.0.2.255,8h

Ainda para cada guest que planeje ter pode definir um ip estatico pelo seu mac address, adcionando um linha para cada conforme o exemplo:

dhcp-host=11:22:33:44:55:66,10.0.2.5  

E recarregue sua rede

# /etc/init.d/networking restart  

Agora com o host configurado é a hora do guest, lance sua maquina virtual com o comando final:

kvm -monitor stdio -smp 2 -m 360 -localtime -hda debian5.qcow -boot c
-net nic,macaddr=1a:2b:3c:4d:5e:6f,model=rtl8139,vlan=0
-net vde,vlan=0,sock=/var/run/vde2/tap0.ctl
-name "debian-lenny"

Agora na VM configure a rede:

# ifconfig eth0 up 
# dhclient eth0
# ping 10.0.2.1
# ifconfig eth0 gateway 10.0.2.1
# ifconfig eth0

Presto! O ultimo comando vai mostrar o ip que o guest recebeu (se reservou o ip estático deve ser o 10.0.2.5), o ip do host é 10.0.2.1.

Agora para deixar melhor instale o servidor SSH para acessar a maquina da forma como é devido:

# aptitude install openssh-server  

Agora você pode entrar na maquina usando o ssh como root na primeira vez para criar seu usuário, e depois iniciar a vm com a opção "--nographic" e passar a entrar apenas com o SSH.

Divirta-se!

Last Updated on Tuesday, 23 February 2010 01:37
 

Troca de mensagens com criptografia em Java, usando AES.

E-mail Print PDF
O escopo de uma aplicação incluía a troca de mensagens sensíveis como parte de um WebService, e além do uso de HTTPS, deveria ser usada alguma forma de criptografia na parte crítica do serviço.

Logo deveria haver uma forma de criptografia disponível tanto no servidor como nos clientes, inicialmente um serviço em Java e o cliente seria no Android (depois outros surgiriam), com isso tem quer ser usado um algorítimo de chave simétrica, que permitisse recuperar a mensagem original de posse da chave de criptografia usada. Obviamente deveria ser um mecanismo eficiente.

Escolhi o AES por acreditar ser o padrão ideal atualmente, "lançado" em 2001 entrou em estudo pelo governo dos EUA e hoje é o seu padrão para troca de documentos, incluindo os "tops". Usando mesmo uma chave de apenas 128bits seria extremamente seguro, a chave pode ser de 128bits, 192bits ou 256bits.

Se você está desenvolvendo em Java EE 5 ou no Android (Ou qualquer Java 6, acredito eu, mas não confirmei), ambos já possuem a implementação deste algoritmo, caso a plataforma não possua você pode usar a implementação do Bouncy Castle, geralmente basta o Provider do bcprov-jdk16-*.jar .

O primeiro passo para a criptografia é definir uma chave, ela pode ser gerada automaticamente pelos mecanismos do sistema:

public byte[] key() throws NoSuchAlgorithmException {
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(128);
SecretKey key = keyGen.generateKey();
return key.getEncoded();
}

Esse método retorna o array de bytes de uma chave de 128bits, podemos usar 256bits mas esse algorismo nem sempre está disponível ou requer configurações especificas do JRE.

A criptografia trabalha com valores em Hexadecimal, assim a chave também usa valores em hexa, você pode criar uma chave com um array de hexas do tamanho que deseja da chave, uma chave de 128bits são 8 bytes (128/16), assim:

byte[] key = { 0x0f, 0xad, 0x54, 0x12, 0x00, 0xaf , 0x34, 0xff }

Em posse de uma chave de criptografia basta aplica-la a uma engine de criptografia, junto dos bytes da mensagem a ser encriptada, com o seguinte método:

public byte[] encode(byte[] input, byte[] key) throws NoSuchAlgorithmException, 
InvalidKeyException, IllegalBlockSizeException,
BadPaddingException, NoSuchPaddingException {
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
byte[] encrypted = cipher.doFinal(input);
return encrypted;
}

Detalhe para o "AES/ECB/NoPadding" , isso usa o algoritmo AES e não faz o padding da mensagem.

Por trabalhar em hexadecimal, o algoritmo trabalha com blocos de 16 bytes, então a mensagem(seus bytes) devem ter uma tamanho múltiplo de 16 para não haver erros de "BadPaddingException: pad block corrupted, para isso podemos usar os seguintes métodos, que preenchem os espaços que faltam até formar o tamanho necessário com "null":

public String nullPadString(String original) {
StringBuffer output = new StringBuffer(original);
int remain = output.length() % 16;
if (remain != 0) {
remain = 16 - remain;
for (int i = 0; i < remain; i++) {
output.append((char) 0);
}
}
return output.toString();
}

Então podemos encriptar uma mensagem assim:

String mensagem = "Hello World!";
byte[] enc = encode(nullPadString(mensagem).getBytes(), key);

Para descriptografar a mensagem não tem mistério, basta o seguinte método:

public byte[] decode(byte[] input, byte[] key) throws NoSuchAlgorithmException, 
InvalidKeyException, IllegalBlockSizeException,
BadPaddingException, NoSuchPaddingException {
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
byte[] decrypted = cipher.doFinal(input);
return decrypted;
}

A única diferença é o parâmetro passado ao Cipher, para de descriptografar.

Agora que podemos criptografar e decodificar as mensagens, precisamos garantir seu formato ao ser enviado via HTTP/HTTPS.

Novamente, como trabalhamos com Hexadecimal, ele não é simplesmente convertido para String, não sem perda, e a conversão de volta também traria riscos, ainda mais se for em plataformas diferentes.

Precisa-se então converter esses valores hexadecimais para uma representação "por extenso" destes, e que possa ser transformado de volta, e podemos alcançar isto com os seguintes métodos:

public String fromHex(byte[] hex) {
StringBuffer sb = new StringBuffer();
for (int i=0; i < hex.length; i++) {
sb.append( Integer.toString( ( hex[i] & 0xff ) + 0x100, 16).substring( 1 ) );
}
return sb.toString();
}

public byte[] toHex(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i+1), 16));
}
return data;
}

Então para enviar uma mensagem podemos proceder da seguinte forma (contando que ambos possuem a mesma chave):

String mensagem = "Hello World!";
byte[] enc = encode(nullPadString(mensagem).getBytes(), key);
String msgParaEnviar = fromHex(enc);

Enviamos então via http a string msgParaEnviar, e ao recebe-la procedemos a decodificação:

byte[] msgEnc = toHex(msgRecebida);
byte[] msg = decode(msgEnc,key);
String mensagem = new String(msg).trim();

Assim temos a mensagem nas duas pontas, o trim é necessário devido ao "padding".

Veja o código da Classe de Criptografia que uso.

Last Updated on Monday, 14 December 2009 01:32
 

PHPDocumentor: Documentação de API em PHP

E-mail Print PDF

Outra parte importante em um projeto, principalmente se está tratando de pacotes públicos e frameworks, é uma documentação completa. Mas documentar é um saco.

Parte da documentação incluí a API de classes, com os pacotes, classes, métodos e suas descrições. O padrão para documentação destes são o docblocks, ou phpdoc, que são comentários no código, sobre a classe, propriedade ou método, e possuem descrição e tags padrões para completar a documentação.

< ?php

/**
* Class Foo
*
* This class implements a hello speaker
* @package hello
*/
class Foo {
/**
* this is some useless var
*/
private $bar ;

/**
* Says hello to provided name
* @param string $name this is the name to be said
* @return Foo
*/
public function hello($name="world") {
echo "Hello, ".ucfirst($name)."!";
return $this
}
}

Esses comentários comuns são mais fáceis de escrever durante o desenvolvimento, e acrescentam muito a documentação. O padrão permite descriçoes muito mais complexas que isso, mas na maior parte das vezes não é necessário.

Agora que esses comentários estão em seu lugar basta usar uma ferramenta para transformar o código em documentação de verdade, pronto para ir online e estar disponível para consulta.

O PhpDocumentor é extremamente fácil de usar e instalar, e gera documentações razoáveis. Ele possui bastante opções de formato de saída (html, pdf, xml...) e templates, mas vou fazer só o básico aqui.

Para instalar o PhpDocumentor você pode usar o PEAR ( se você não conhece, está perdendo muito), basta rodar o comando a seguir:

# pear install PhpDocumentor

Se tudo correr bem você pode agora transformar seu código em documentação com a linha a seguir:

$ phpdoc -t docs -o HTML:default:eathli -d src

Nesse caso a documentação será salva na pasta docs, no formato HTML usando o template earthli, através dos arquivos da pasta src.

Basta apontar seu navegador para a pasta docs e navegar na documentação agora.

 

Documentação em PHP
 

 

Confira mais opções comentários, anotações e tudo mais no Manual do PhpDocumentor.

Last Updated on Wednesday, 02 December 2009 12:18
 
  • «
  •  Start 
  •  Prev 
  •  1 
  •  2 
  •  3 
  •  4 
  •  5 
  •  6 
  •  7 
  •  8 
  •  9 
  •  10 
  •  Next 
  •  End 
  • »


Page 1 of 11