Ya avise cuando comencé con esta pagina que no solo hablaría sobre de Delphi. Así que voy a poner por aquí un nuevo juguete con el que estoy entretenido desde hace unos días. Se trata de un pequeño servidor web realizado en C y pensado para ser utilizado en linux. Para hacernos una idea de lo pequeño que es basta decir que el código cuenta con solo unas 600 lineas.
Con ese tamaño no le podemos exigir demasiado, de todas formas ya dije que no aspira a ser mas que un juguete o como mucho una herramienta puntual para transferir archivos de un equipo a otro. Por ahora maneja correctamente los comandos Get y Head , puede enviar archivos, mensajes de texto, listar directorios, codificar/descodificar urls y además es multihilo. Faltan por implementar la autentificación de usuarios, pero eso ya lo haremos mas adelante si es necesario.
El funcionamiento es sencillo, solo hay que ejecutar pico en un terminal y luego en cualquier navegador utilizar la url http://127.0.0.1:1978/ si es en el mismo equipo o http://tuip:1978/ si es desde otro equipo de tu red. Se puede ejecutar sin usar el terminal, pero entonces el programa permanece oculto y no mostraría ni mensajes, ni errores (si los hubiera). Una vez que accedemos a esa url vemos un listado de todo el sistema de ficheros, aquí es importante recordar que el programa tiene los mismos permisos de lectura que el usuario que lo ejecuta (para mas adelante estoy pensando en utilizar algo como chroot para cambiar el directorio raíz del proceso).
Como siempre, se admiten sugerencias, consejos, criticas pero siempre recordando que se trata de un juguete y no de un servidor serio, yo al menos pienso utilizarlo como venia haciéndolo con su hermano gemelo en windows, para pasar archivos de un ordenador a otro rápidamente y sin tener que andar instalando nada, ni cambiando la configuración de las carpetas compartidas, etc ... un par de clicks y copiando, sin complicarse (muy útil cuando quieres pasar archivos al portátil de un amigo).
#include <getopt.h> #include <dirent.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <signal.h> #include <unistd.h> #include <netinet/in.h> #include <sys/select.h> #include <sys/sendfile.h> #include <sys/socket.h> #include <sys/stat.h> #include <sys/types.h> #include <sys/wait.h> const char hex[] = "0123456789ABCDEF"; // Configuracion int buffersize = 8*1024; int inetd = 0; int puerto = 1978; int timeout = 30; // Variables globales char *comando = NULL; char *ruta = NULL; // Imprime mensajes void printmsg(char *str) { if (!inetd) fprintf(stderr,"[%d] %s\n",(int) getpid(),str); } // Imprime mensajes de error void printerror(char *str) { if (!inetd) fprintf(stderr,"[%d] %s: %s\n",(int) getpid(),str,strerror(errno)); } // Concatena dos cadenas int stradd(char **dest, char *src) { char *str; if (!src) return -1; if (*dest) { str = realloc(*dest,strlen(*dest) + strlen(src) + 1); } else { str = malloc(strlen(src) + 1); if (str) memset(str,0,strlen(src) + 1); } if (str) { strcat(str,src); *dest = str; return 0; } return -1; } // Codifica una url int urlencode(char **dest, char *src) { char *str; if (!src) return -1; str = malloc((strlen(src) * 3) + 1); if (str) { *dest = str; memset(str,0,(strlen(src) * 3) + 1); while (*src) { if (((*src >= 'a') && (*src <= 'z')) || ((*src >= 'A') && (*src <= 'Z')) || ((*src >= '0') && (*src <= '9'))) { *str = *src; } else { *str = '%'; str++; *str = hex[*src >> 4]; str++; *str = hex[*src & 0x0f]; } str++; src++; } return 0; } return -1; } // Descodifica una url int urldecode(char **dest, char *src) { char *str; char c; char esc = 0; if (!src) return -1; str = malloc(strlen(src) + 1); if (str) { *dest = str; memset(str,0,strlen(src) + 1); while (*src) { if (*src == '%') { esc = 2; c = 0; } else { switch(esc) { case 2: if (strchr(hex,toupper(*src))) { c = (char)((c + (strchr(hex,toupper(*src))-hex)) << 4); esc = 1; } break; case 1: if (strchr(hex,toupper(*src))) { *str = (char)(c + (strchr(hex,toupper(*src))-hex)); str++; esc = 0; } break; default: if (*src == '+') *str = ' '; else *str = *src; str++; } } src++; } return 0; } return -1; } // Obtiene informacion sobre un archivo int fileinfo(char *dir, char *nombre, struct stat *st) { char *str; int len; int barra = 0; if (!nombre) return -1; if (dir) { len = strlen(dir); if (len > 0) if (dir[len-1]!='/') { len += 1; barra = 1; } } else len = 0; len = len + strlen(nombre) + 1; str = malloc(len); if (str) { memset(str,0,len); if (dir) { strcpy(str,dir); if (barra) strcat(str,"/"); } // str = dir + "\" + nombre strcat(str,nombre); if (stat(str,st) == 0) { free(str); return 0; } free(str); } return -1; } // Escibe las cabeceras de la respuesta void cabeceras(int codigo, char *res, int lon, char *tipo) { printf("HTTP/1.1 %d %s\n",codigo,res); // Si la longitud es menor de cero la ignoramos if (lon > 0) printf("Content-Length: %d\n",lon); // Si se ha determinado el tipo, lo incluimos if (tipo) printf("Content-Type: %s\n",tipo); // Enviamos una linea en blanco printf("\n"); fflush(NULL); } // Envia un mensaje de texto como respuesta void mensaje(int codigo, char *res, char *str) { // Escribimos las cabeceras de la respuesta cabeceras(codigo,res,strlen(str),"text/html"); // Si el comando no es HEAD, escribimos en el socket el mensaje if (strcasecmp(comando,"HEAD") != 0) { printf("%s",str); fflush(NULL); } // Si el codigo es diferente de 200 (OK) lo mostramos if (codigo != 200) printmsg(res); } // Envia un listado con el contenido de un directorio void indice() { char *str; char *url; DIR *dir; struct dirent *ent; struct stat st; // Abrimos el directorio dir = opendir(ruta); if (dir) { str = NULL; if (stradd(&str,"<html>\n<h1>Indice de ") == 0) { stradd(&str,ruta); stradd(&str,"</h1>\n<pre>\n"); // Recorremos el directorio while (ent = readdir(dir)) { // Obtenemos informacion sobre el archivo if (fileinfo(ruta,ent->d_name,&st) == 0) { // Lo agregamos al listado stradd(&str,"<a href=\""); urlencode(&url,ent->d_name); stradd(&str,url); free(url); // Si es un directorio agregamos una '/' al final if (S_ISDIR(st.st_mode)) stradd(&str,"/"); stradd(&str,"\">"); stradd(&str,ent->d_name); stradd(&str,"</a>\n"); } } stradd(&str,"</pre>\n</html>"); // Enviamos el listado mensaje(200,"OK",str); free(str); } closedir(dir); } } // Envia un archivo void enviar() { struct stat st; int f; off_t offset = 0; // Comprobamos si el archivo existe if (fileinfo(NULL,ruta,&st) == 0) { // Comprobamos si es un directorio if (S_ISDIR(st.st_mode)) { // Si es un directorio enviamos su indice indice(ruta); } else // Si es un archivo intentamos leerlo if ((f = open(ruta, O_RDONLY)) != -1) { // Escribimos las cabecera cabeceras(200,"OK",st.st_size,NULL); // Si el comando no es HEAD enviamos el fichero if (strcasecmp(comando,"HEAD") != 0) { sendfile(STDOUT_FILENO,f,&offset,st.st_size); } close(f); } else // Respondemos que no encontramos el archivo mensaje(404,"Not Found","<html><h1>Not Found</h1></html>"); } else // Respondemos que no encontramos el archivo mensaje(404,"Not Found","<html><h1>Not Found</h1></html>"); } // Lee una linea, utilizando un buffer int readline(char *buf, char *line, int count) { int tout; fd_set fds; struct timeval tv; int retval; char *c; // 20 * 50000 us = 1 segundo tout = timeout * 20; // No salimos del bucle hasta encontrar '\n' o llenar el buffer while ((!strchr(buf,'\n')) && (strlen(buf) < count)) { FD_ZERO(&fds); FD_SET(STDIN_FILENO, &fds); tv.tv_sec = 0; tv.tv_usec = 50000; // Comprobamos si hay algo que leer retval = select(STDIN_FILENO + 1, &fds, NULL, NULL, &tv); if (retval == -1) { // Ignoramos la interrupciones if (errno = EINTR) continue; printerror("select()"); return -1; } if (retval) { // Reseteamos el timeout tout = timeout * 20; // Apuntamos al caracter nulo c = buf + strlen(buf); // Escribimos el texto nuevo a continuacion del anterior retval = read(STDIN_FILENO, c, count - strlen(buf)); if (retval == -1) { printerror("read()"); return -1; } if (retval == 0) return -1; // Colocamos el caracter nulo al final del texto recibido c[retval] = '\0'; } else { --tout; // Si se cumple el timeout if (tout < 1) { // Salimos mensaje(408,"Request Timeout","<html><h1>Request Timeout</h1></html>"); return -1; } } } // Si encontramos el caracter '\n' if (strchr(buf,'\n')) { // Comenzamos a copiar la linea c = buf; while (*c != '\n') { // Eliminamos el caracter '\r' if (*c != '\r') { *line = *c; line++; } c++; } // Colocamos el carcater nulo al final de la linea *line = '\0'; // Desplazamos el resto del texto c++; while (*c) { *buf = *c; buf++; c++; } // Colocamos el caracter nulo al final del texto *buf = '\0'; return 0; } // Si llegamos aqui, es que se ha llenado el buffer mensaje(414,"Request Too Long","<html><h1>Request Too Long</h1></html>"); return -1; } void conexion() { char *buf; char *linea; char *saveptr; char *token; char *str; // Reservamos memoria para buf = malloc(buffersize); if (buf) { linea = malloc(buffersize); if (linea) { // Bucle de la conexion while (1) { if (readline(buf,linea,buffersize - 1) == -1) break; // Ignoramos las lineas en blanco if (strlen(linea) > 0) { // Mostramos la peticion printmsg(linea); // Separamos el comando token = strtok_r(linea, " \t\n", &saveptr); if (token && (strcasecmp(token,"GET") == 0) || (strcasecmp(token,"HEAD") == 0)) { comando = strdup(token); // Separamos el documento token = strtok_r(NULL, " ?\t\n", &saveptr); if (token) { ruta = strdup(token); // Ignoramos el resto de cabeceras (por ahora) do { if (readline(buf,linea,buffersize - 1) == -1) { free(buf); free(linea); free(comando); free(ruta); return; } } while (strlen(linea)>0); // Descodificamos el nombre del documento if (urldecode(&str,ruta) == 0) { // Intentamos enviar el documento free(ruta); ruta = str; enviar(); } else mensaje(404,"Not Found","<html><h1>Not Found</h1></html>"); free(ruta); } free(comando); } } } free(linea); } free(buf); } } void signalhandler(int signal_numer) { int retval; int status; switch(signal_numer) { case SIGCHLD: // Limpiamos los zombies do { retval = waitpid(-1,&status,WNOHANG); } while ((retval != 0) && (retval != -1)); break; } } void servidor() { struct sigaction sa; int servidor, cliente; struct sockaddr_in sin; fd_set fds; struct timeval tv; int retval; // Registramos el manejador memset(&sa,0,sizeof(sa)); sa.sa_handler = &signalhandler; if (sigaction(SIGCHLD, &sa, NULL) != 0) { printerror("sigaction()"); return; } // Creamos el socket if ((servidor = socket(AF_INET, SOCK_STREAM, 0)) != -1) { sin.sin_family = AF_INET; sin.sin_port = htons(puerto); sin.sin_addr.s_addr = htonl(INADDR_ANY); // bind() if (bind(servidor,(struct sockaddr *) &sin,sizeof(struct sockaddr_in)) == 0) { // listen() if (listen(servidor, 16) == 0) { // Bucle principal while (1) { FD_ZERO(&fds); FD_SET(servidor, &fds); tv.tv_sec = 0; tv.tv_usec = 50000; // Comprobamos si hay conexiones esperando - select() retval = select(servidor + 1, &fds, NULL, NULL, &tv); if (retval == -1 ) { // Ignoramos la interrupciones if (errno = EINTR) continue; printerror("select()"); break; } // Si hay conexiones esperando, las aceptamos if (retval) { retval = sizeof(sin); cliente = accept(servidor, (struct sockaddr *) &sin, &retval); if (cliente != -1) { // Creamos un proceso "hijo" - fork() retval = fork(); if (retval == -1) { printerror("fork()"); break; } if (retval == 0) { // Estamos dentro del proceso hijo close(servidor); if (dup2(cliente,STDOUT_FILENO) != -1) if (dup2(cliente,STDIN_FILENO) != -1) conexion(); else printerror("dup2()"); else printerror("dup2()"); close(cliente); // Aqui termina el proceso hijo exit(0); } close(cliente); } else printerror("accept()"); } } } else printerror("listen()"); } else printerror("bind()"); // Cerramos el socket close(servidor); } else printerror("socket()"); } void printhelp(int exitcode) { fprintf(stderr,"Uso: pico [opciones]\n"); fprintf(stderr," -b --buffer\n"); fprintf(stderr," Tamaño en Kb del buffer [8..32]\n"); fprintf(stderr," -h --help\n"); fprintf(stderr," Muestra este mensaje de ayuda.\n"); fprintf(stderr," -i --inetd\n"); fprintf(stderr," Permite ejecutar el servidor usando xinetd.\n"); fprintf(stderr," -p --puerto\n"); fprintf(stderr," Numero del puerto del servidor [1..65535]\n"); fprintf(stderr," -t --timeout\n"); fprintf(stderr," Tiempo de espera antes de cerrar la conexion, en segundos [5..300]\n\n"); exit(exitcode); } int main(int argc, char *argv[]) { int i; int nextopt; char *str; const struct option options[] = { {"buffer", 1, NULL, 'b'}, {"help", 0, NULL, 'h'}, {"inetd", 0, NULL, 'i'}, {"puerto", 1, NULL, 'p'}, {"timeout", 1, NULL, 't'}, {NULL, 0, NULL, 0} }; do { nextopt = getopt_long(argc,argv,"b:hip:t:",options,NULL); switch (nextopt) { case 'b': case 'p': case 't': i = strtol(optarg,&str,10); if (*str != '\0') { fprintf(stderr,"Parametro incorrecto: %s\n",optarg); printhelp(1); } switch (nextopt) { case 'b': if ((i < 8) || (i > 32)) { fprintf(stderr,"El tamaño del buffer es incorrecto.\n"); printhelp(1); } buffersize = 1024*i; break; case 'p': if ((i < 1) || (i > 65535)) { fprintf(stderr,"El numero del puerto es incorrecto.\n"); printhelp(1); } puerto = i; break; case 't': if ((i < 5) || (i > 5*60)) { fprintf(stderr,"El timeout es incorrecto.\n"); printhelp(1); } timeout = i; break; } break; case 'h': printhelp(0); case 'i': inetd = 1; break; case '?': fprintf(stderr,"Parametro no valido\n"); printhelp(1); break; } } while (nextopt != -1); if (inetd) conexion(); else { fprintf(stderr,"Pid: %d Puerto: %d Buffer: %d Bytes Timeout: %d sg\n\n", (int) getpid(),puerto,buffersize,timeout); servidor(); } return 0; }