domingo, 14 de diciembre de 2008

Inteligencia Artificial: los chatbots


Actualmente dedico la mayor parte de mi tiempo libre al proyecto OpenDomo, lo que me resta tiempo para investigar sobre seguridad y criptografía, que siempre han sido mis hobbies principales. Así pues, a partir de ahora, la temática del blog girará alrededor de OpenDomo, tratando de seguridad, Inteligencia Artificial, electrónica o lo que se cueza en ese momento en dicho proyecto.

Con este objetivo y el de ampliar un poco los horizontes del blog, he decidido crear una nueva sección de Inteligencia Artificial, donde iré publicando pequeños artículos de todo tipo, tal y como he ido haciendo con las seguridad informática.

Parecería lógico empezar esta entrada sobre Inteligencia Artifical con una definición del concepto, pero resulta que el tema es motivo de conflicto. Dado que no existe una definición totalmente aceptada he decido tirar de la más polémica: la usada en el Premio Loebner.

En 1950, Alan Madison Turing en su artículo Computing Machinery and Intelligence se planteó si las máquinas podían llegar a pensar, y en caso de poder hacerlo, cómo podríamos saberlo. La conclusión de Turing fué que sabremos que una máquina piensa cuando sus respuestas sean indistinguibles de las de un humano.

Así, en 1990 Hugh Loebner implementaba un Test de Turing, en el que se ofrecía 100.000$ y una medalla de oro a la primera computadora cuyas respuestas fuesen indistinguibles de las de un humano. Daba origen así al Premio Loebner.


Con el objetivo de ganar dicho galardon y marcar un antes y un después en la historia de la IA son muchos los que lo han intentado. Bastan unas pocas búsquedas en google para encontrar gran cantidad de chatbots.

Si bien es cierto que desarrollar un buen chatbot es realmente difícil, hasta el punto de que hoy por hoy, ninguno ha conseguido pasar el test de Turing, es sorprendente lo fácil que es crear un bot que 'de el pego'.

Adjunto uno que he hecho en shellscript en unas pocas lineas.

#!/bin/bash

DATABASE=database

touch $DATABASE

while true; 
do
   read -p "humano: " line
   echo $line >> database
   
   data_size=`wc -l $DATABASE | cut -d' ' -f1`
   nline=$(( $RANDOM % $data_size + 1 ))
   new_line=`tail -$nline $DATABASE | head -1`
   echo "bot: $new_line"
done



Como es lógico, no se puede esperar mucha inteligencia de este bot, pero aun y así, se desprenden conversaciones sorprendentes.

Empezando con una base de datos simple como la siguiente:


$ cat database
Hola
Que tal estas?
Me llamo bot y soy un Bot
Como te llamas? 

He tenido la siguiente conversación:



humano: Hola
bot: Me llamo bot y soy un Bot
humano: Hola Bot
bot: Hola
humano: Quieres conversar conmigo?
bot: Hola Bot
humano: No, bot eres tu
bot: Que tal estas?
humano: bien
bot: Me llamo bot y soy un Bot
humano: ya lo se
bot: Como te llamas?
humano: Dani
bot: Hola Bot
humano: ese eres tu     
bot: ese eres tu
humano: no, tu
bot: Hola
humano: me estas mosqueando ...
bot: me estas mosqueando ...
humano: por que te mosqueas?   
bot: Como te llamas?
humano: Dani
bot: Dani
humano: Si, dime
bot: Hola Bot
humano: que no, que soy Dani
bot: Me llamo bot y soy un Bot
humano: vale, captado
bot: por que te mosqueas?
humano: es que eres muy pesado
bot: no, tu
humano: yo no soy pesado!
bot: Si, dime
humano: Que no soy pesado
bot: Que no soy pesado
humano: que no
bot: ya lo se
humano: ah, bueno
bot: Quieres conversar conmigo?
humano: eso estamos haciendo
bot: vale, captado
humano: entonces, de que quieres hablar?
bot: Dani
humano: Si
bot: me estas mosqueando ...
humano: ui!
bot: vale, captado

Aunque la conversación carece de sentido, lo cierto es que no resulta del todo inverosímil. El problema con este tipo de bots y otros de más sofisticados pero que usan las mismas técnicas, es que no llegan a comprender las frases. Se dedican simplemente a realizar construcciones habituales y a repetir frases. Sin duda, no parece la técnica adecuada para superar el Test de Turing.





domingo, 7 de diciembre de 2008

Cómo crear usuarios en C / Linux

En ocasiones necesitamos crear usuarios desde un programa en C y aunque podría bastar haciendo una llamada a 'useradd' siempre es más elegante usar las llamadas al sistema. En este artículo voy a explicar cómo crear un usuario con su correspondiente registro en /etc/passwd y en /etc/shadow.

Lo primero que necesitamos es una estructura donde guardar los datos que almacenaremos en /etc/passwd, para usar esta estructura 'struct passwd' necesitaremos la cabecera 'pwd.h'.

struct passwd p;

p.pw_name = "myusername";
p.pw_passwd = "x";
p.pw_uid = 1000;
p.pw_gid = 1000;
p.pw_gecos = "Test User";
p.pw_dir = "/home/myusername";
p.pw_shell = "/bin/sh";


A destacar la 'x' en pw_passwd, pues recordemos que estamos usando shadow passwords y por lo tanto la password se guardará en /etc/shadow. Respecto a los demas campos, a elegir.

Aun así, es interesante ver como podemos escoger un id para un usuario para no repetirlo.

struct passwd *pw = NULL;
int min = 1000;
int max = 65000;

f = fopen("/etc/passwd", "r");

/* check user and get valid id */
while ((pw = fgetpwent(f)))
{
if (strcmp(pw->pw_name, p.pw_name) == 0)
{
perror("user_add(): user exists");
exit(0);
}

if ((pw->pw_uid >= p.pw_uid) && (pw->pw_uid <>pw_uid >= min))
{
p.pw_uid = pw->pw_uid + 1;
}
}

fclose(f);



Ya tenemos la entrada de /etc/passwd preparada, así que la añadiremos al fichero de la siguiente manera:


f = fopen("/etc/passwd", "a+");
if(!f)
{
perror("user_add(): cannot open /etc/passwd");
exit(0);
}

if (putpwent(&p, f) == -1)
{
perror("user_add(): putpwent() error");
exit(0);
}

fclose(f);




Vayamos ahora a por las shadow. El proceso es similar al anterior, solo que ahora usaremos una estructura 'struct spwd sp;' para la cual necesitamos la cabecera 'shadow.h'.
Además ha llegado el momento de crear la contraseña, para lo que usaremos la función 'crypt()'. Esta a su vez, necesita una salt, que la crearemos tal y como vemos a continuación.



struct timeval tv;
static char salt[40];

salt[0] = '\0';

gettimeofday (&tv, (struct timezone *) 0);
strcat(salt, l64a (tv.tv_usec));
strcat(salt, l64a (tv.tv_sec + getpid () + clock ()));

if (strlen (salt) > 3 + 8)
salt[11] = '\0';



Esta forma de crear la salt es bastante común, aunque hay otros enfoques válidos. Al final, se trata de genererar diferentes salt cada vez que se crea un usuario.

Vamos ahora a rellenar la estructura shadow:


#define DAY (24L*3600L)
#define WEEK (7*DAY)
#define SCALE DAY
...
struct spwd sp;
sp.sp_namp = p.pw_name;
sp.sp_pwdp = (char*)crypt(passwd, salt);
sp.sp_min = 0;
sp.sp_max = (10000L * DAY) / SCALE;
sp.sp_lstchg = time((time_t *) 0) / SCALE;
sp.sp_warn = -1;
sp.sp_expire = -1;
sp.sp_inact = -1;
sp.sp_flag = -1;



El segundo campo corresponde a la contraseña cifrada en DES, de la forma tradicional y poco segura. Este parámetro se puede mejorar usando otras funciones de cifrado.
Los parámetros siguientes indican los dias de cambios de password, expiración de la cuenta, etc. Ver detalles mediante 'man shadow'.

Finalmente, solo nos queda añadir el registro en /etc/shadow:

f = fopen("/etc/shadow", "a+");
if(!f)
{
perror("user_add(): cannot open /etc/shadow");
exit(0);
}

if (putspent(&sp, f) == -1)
{
perror("user_add(): putspent() error");
exit(0);
}

fclose(f);




Algunos cambios interesantes son usar una función de cifrado más segura, bloquear los archivos /etc/passwd y /etc/shadow antes de abrirlos, y verificar en /etc/shadow que no existe ningun registro que coincida con el que vamos a añadir, tal y como hemos hecho con /etc/passwd. Tambien quedaría realizar una gestión del grupo, como por ejemplo, crearlo si es necesario. En cualquier caso quedan cubiertos los objetivos de este artículo, ver como se hece en leguaje C para crear un usuario de Linux.

A propósito, borrar un usuario consisitiría, simplemente, en eliminar la linea correspondiente de /etc/passwd y /etc/shadow. Así pues, que nadie me pida que explique cómo hacer un programa en c para borrar usuarios ;)