Un blog mas

Bitácora de vuelo

AES (Advanced Encription Standard): Es un algoritmo de cifrado simétrico por bloques basado en el desarrollo de Joan Daemen y Vicent Rijmen bajo el nombre de Rijndael (pronunciado “Reindal” para los que no sean belgas), transformado en estándar en mayo de 2002.

Como funciona AES

Uniendo tres buenas ideas:

1.- Confusión: La primer idea es ocultar la relación entre el mensaje en texto claro y el mensaje cifrado. Un ejemplo es el viejo y conocido cifrado del César.

2.- Difusión: La segunda idea fue esparcir el mensaje original, en este caso aplicando una simple transposición de columnas.

3.- Sólo la clave es secreta: La tercer y última idea es asumir que el método en algún momento será conocido, y la fortaleza del cifrado estará apoyada solo en la clave.

El algoritmo se basa en varias sustituciones, permutaciones y transformaciones lineales ejecutadas en bloques de datos, repitiendo estas operaciones varias veces (o rondas) dependiendo de la longitud de la clave que puede ser de 128, 192 y 256 bits (llamado AES-128, AES-192 o AES-256 respectivamente).

Modo de Operación de AES

ECB: Electronic CodeBook, se dividen los mensajes en bloques y se cifran por separado utilizando la misma clave. La desventaja es que bloques de texto claro identico generan bloques identicos de texto cifrado. Es por eso que esta desaconsejado el uso de ECB.

CBC: Cipher-block Chaining, se agrega el IV o vector de inicialización, un código el cual aplicamos una operación XOR junto con el primer bloque en texto claro, se cifra con la clave como describe el algoritmo, y este primer bloque cifrado además será utilizado como el NUEVO IV para el siguiente bloque del mensaje. La desventaja es que el cifrado pasa a ser un proceso secuencial y por lo tanto no puede ser paralelizado.

PCBC: Propagating Cipher Block Chaining, opera de manera similar al método CBC con pequeña diferencia. Para obtener el NUEVO IV del siguiente bloque, lo que se se aplicara es una operación XOR sobre los bloques cifrados y en texto claro anteriores.

Los métodos descriptos hasta aquí operan estrictamente sobre bloques. Si el mensaje no tiene una longitud múltiplo del tamaño del bloque se le añadirá al último un relleno o padding para llegar a dicha longitud. Los algoritmos que describiremos a continuación se denominan de flujo o keystream, con la propiedad de poder soportar perdida de bloques tolerando algunos cortes en el flujo, lo que nos permite recuperar parte de la información sin la completitud del mensaje. Este tipo de cifrado se puede utilizar por ejemplo para transmisiones de audio o video.

CFB: Cipher feedback, es un pariente cercano del CBC, con la diferencia de que el bloque cifrado con la clave en este caso será el IV, el cual genera un IV CIFRADO que al cual se aplicará un XOR con el primer bloque de texto claro para obtener así el primer bloque de texto cifrado; quien a su vez se convertirá en el NUEVO IV, el cual será nuevamente cifrado para continuar el flujo del mensaje aplicando una vez mas una funcion XOR con el segundo bloque de texto claro, y así sucesivamente. Se puede observar que ahora si podríamos recuperar cualquier bloque de texto en claro siempre y cuando tengamos disponible el siguiente bloque cifrado y el bloque cifrado anterior (o el IV para el primer caso).

OFB: Output feedback, tiene una modificación en su función que permite independizarse del bloque de texto cifrado siguiente ya que lo que se convertirá en el NUEVO IV es el IV CIFRADO del bloque consecutivo sin la operación XOR que lo vincula con el bloque en texto claro. Para entenderlo mejor es conveniente observar el esquema de OFB_decryption

CTR: Counter, agrega al OFB un contador o número de secuencia que se combina con el IV (también llamado NONCE) para poder paralelizar la función de cifrado ahora independiente del bloque que se opere ya ahora se mantiene el número de orden del bloque.

Cifrando un texto con OpenSSL desde la línea de comandos

// Transformar el IV y la clave a Hexa
pablo@laptop: hexiv=$(echo -n '0000000000000000' | hexdump -v -e '/1 "%02X"')                 // -e Format String con un Hex Byte sin separadores
pablo@laptop: hexpass=$(echo -n 'mysecret' | hexdump -v -e '/1 "%02X"')                       // -e Format String con un Hex Byte sin separadores
pablo@laptop: echo -n '12345' | openssl enc -aes-256-cbc -iv "$hexiv" -K "$hexpass" -a -p     // -a para imprimir la salida en Base64.
salt=0000000000000000                                                                         // -p imprime los parámetros utilizados
key=6D79736563726574000000000000000000000000000000000000000000000000
iv =30303030303030303030303030303030
4HdSmrDa3s7Hc7+cTVMtHg==

Cifrando un texto con PHP desde la línea de comandos

pablo@laptop: php -r 'echo( openssl_encrypt ("12345", "aes-256-cbc","mysecret",false,"0000000000000000"));'           // KEY e IV en Text
4HdSmrDa3s7Hc7+cTVMtHg==

Cifrar y descifrar un texto con un Form y PHP

<html>
<head>
    <title>Aplicacion de Cifrado</title>
</head>
<body>
    <div align="center">
    <h1>AES-256</h1>
    <form method="POST">
        Clave: <input type="text" id="key" name="key" size=25><br>
        Texto: <input type="text" id="text" name="text" size=25><br>
        <?php
        $array = array("aes-256-cbc","aes-256-cfb","aes-256-ecb","aes-256-ofb");
        echo "<select id=\"meth\" name=\"meth\" style=\"display: none;\">\n";
        foreach ($array as $valor){
            echo "<option value=\"$valor\">$valor</option>\n";
        }
        echo "</select>";
        ?>
        <input type="radio" name="action" value="crypt" checked  onclick="getElementById('meth').style.display='none'">Cifrar
        <input type="radio" name="action" value="decrypt" onclick="getElementById('meth').style.display='block'">Descifrar<br>
        <input type="submit" value="Aceptar">
    </form>
    <?php
    if ($_POST["action"]=="crypt"){
    foreach ($array as $valor){
        $text = openssl_encrypt ($_POST["text"], $valor,$_POST["key"],false,"0000000000000000");
        echo "<b>" . strtoupper($valor) . ":</b> $text<br>";
    }
    }elseif ($_POST["action"]=="decrypt"){
        $text = openssl_decrypt ($_POST["text"], $_POST["meth"] ,$_POST["key"],false,"0000000000000000");
        echo "<b>Texto Claro:</b> $text";
    }
    ?>
    </div>
</body>
</html>

Referencias:
How to make openssl encrypt passwords like php via command line
https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Feedback_(CFB)

Continuando la seguidilla de entradas con Arduino, vamos a comentar como se fue dando el desarrollo de esta básica interfaz web para poder administrar los parámetros de red del Ethernet Shield W5100 conectado a nuestro Arduino. La idea era poder enviar los parámetros por POST, usar Basic Authentication para el acceso a la sección de configuración y poder restaurar los valores a un estado original con un botón de reset.

Librerías utilizadas

Para resolver el servicio web, el proyecto Webduino tiene muy desarrollada la librería WebServer.h (disponible aquí) que nos simplifica el intercambio de datos vía POST y resolver el acceso con Basic Authentication similar al del .htaccess de Apache.
Para poder almacenar los datos en memoria utilizamos la librería EEPROM.h (disponible aquí) que implementa los métodos write y read para escribir y leer de manera sencilla sobre la memoria del Arduino.
SoftReset.h (disponible aquí) es una implementación que permite hacer un reseteo de la placa Arduino desde el mismo software.
Por último incorporamos rBase64.h (disponible aquí) que nos permite codificar y decodificar a Base64.

Basic Access Authentication

Es un método que permite que un agente de usuario se autentique enviando en la cabecera HTTP usuario:contraseña en Base64.
Por ejemplo para usuario admin y contraseña admin, si codificamos admin:admin en base64 obtenemos YWRtaW46YWRtaW4= . Para verificar esto podríamos utilizar el servicio web de https://www.base64encode.org/.

Authorization: Basic YWRtaW46YWRtaW4=

Si observamos las siguientes lineas del código, el método checkCredentials verifica si los parámetros enviados por el cliente son iguales al usuario:contraseña almacenados en la variable cUserPass64. Si no coinciden se envía un HTTP 401 Unauthorized status.

char cUserPass64[45] = "YWRtaW46YWRtaW4=";              // admin:admin en base64
.
.
.
if (client.checkCredentials(cUserPass64)){   
    client.httpSuccess();
}else{
    client.httpUnauthorized();
}

Recibiendo los parámetros del formulario por POST

El método readPostparam() de la clase WebServer dentro de un loop nos permite recibir todos los POST REQUEST y poder asignarlos a una variable del programa, como por ejemplo ip_dns[].

if (type == WebServer::POST){
    bool repeat;
    char name[16];
    char value[16];
 
    do{
      repeat = client.readPOSTparam(name, 16, value, 16);
    .
    .
    .     
       ip_dns[(int)name[1] - 48]=strtoul(value, NULL, 10);
    .
    .
    .
    } while (repeat);

Para el caso de un cambio de contraseña del usuario admin, debemos concatenar anteponiendo admin: a la nueva contraseña y codificarla en Base64. Esto lo podemos ver en las siguientes líneas del programa.

strcpy(cUserPass, "admin:");
strcat(cUserPass, value);
rbase64.encode(cUserPass).toCharArray(cUserPass64,45);

Escribiendo la memoria del Arduino
Los métodos write y read de la clase EEPROM nos permiten acceder directamente a una posición de memoria como se observa en las siguientes lineas de nuestro programa donde escribimos la IP entre las posiciones de memoria 7 y 10.

EEPROM.write(7, ip[0]);
EEPROM.write(8, ip[1]);
EEPROM.write(9, ip[2]);
EEPROM.write(10, ip[3]);

De manera similar, accedemos para su lectura y asignación a la variable ip[].

ip[0] = EEPROM.read(7);
ip[1] = EEPROM.read(8);
ip[2] = EEPROM.read(9);
ip[3] = EEPROM.read(10);

Nosotros definimos un byte definido en la constante ID_KNOW y almacenada en la primer posición de memoria que nos indica si la misma ya fue escria en alguna iteración o se toman los valores de red definidos por defecto. Esto se puede verificar en la sección ShieldSetup.

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED};
byte ip[] = {192,168,1,2};
byte subnet[] = {255,255,0,0};
byte ip_gateway[] = {192,168,1,1};
byte ip_dns[] = {192,168,1,1};
char cUserPass[] = "admin:admin";
char cUserPass64[45] = "YWRtaW46YWRtaW4=";              // admin:admin en base64
 
const byte ID_KNOW = 0x92;                              // Usado para identificar si se grabaron datos en la EEPROM 
char buffer[100];
void ShieldSetup(){
  //  Serial.begin(9600);
  int idcheck = EEPROM.read(0);
 
  if (idcheck == ID_KNOW){
    mac[0] = EEPROM.read(1); mac[1] = EEPROM.read(2); mac[2] = EEPROM.read(3); mac[3] = EEPROM.read(4); mac[4] = EEPROM.read(5); mac[5] = EEPROM.read(6); 
    ip[0] = EEPROM.read(7); ip[1] = EEPROM.read(8); ip[2] = EEPROM.read(9); ip[3] = EEPROM.read(10); 
    subnet[0] = EEPROM.read(11); subnet[1] = EEPROM.read(12); subnet[2] = EEPROM.read(13); subnet[3] = EEPROM.read(14); 
    ip_gateway[0] = EEPROM.read(15); ip_gateway[1] = EEPROM.read(16); ip_gateway[2] = EEPROM.read(17); ip_gateway[3] = EEPROM.read(18); 
    ip_dns[0] = EEPROM.read(19); ip_dns[1] = EEPROM.read(20); ip_dns[2] = EEPROM.read(21); ip_dns[3] = EEPROM.read(22); 
    for (int i=0;i<43;i++){
      cUserPass64[i] = EEPROM.read(i+23); 
    }
  }
  Ethernet.begin(mac, ip, ip_dns, ip_gateway, subnet);
}

La interfaz web

Todo esto nos brinda un entorno para la administración de los parámetros de red protegido con usuario y contraseña pero nos restaba poder volver a los valores por defecto en el caso de perder la contraseña de administrador o el acceso de red. Imitando el comportamiento de los routers hogareños se nos ocurrió utilizar una de las entradas/salidas del Arduino para agregarle un botón de reset que se comporte como reinicio del dispositivo si se oprime por un instante, o reinicio a los valores establecidos por default si se mantiene por mas de 3 segundos.

Para ello fue necesario agregar un pulsador con una resistencia de 4,7k a 10k en Pull Down (ver el siguiente esquema).

El cableado

Las resistencias de Pull-Down o Pull-Up se conectan entre el PIN digital y una de las tensiones de referencia (0V o 5V) y “fuerzan” (de ahí su nombre) el valor de la tensión a LOW o HIGH, respectivamente.

La resistencia de Pull-Up fuerza HIGH cuando el pulsador está abierto. Cuando está cerrado el PIN se pone a LOW, la intensidad que circula se ve limitada por esta resistencia
La resistencia de Pull-Down fuerza LOW cuando el pulsador está abierto. Cuando está cerrado el PIN se pone a HIGH, y la intensidad que circula se ve limitada por esta resistencia.

El código de reset

Definiendo algunas constantes y variables en el programa, solo debemos monitorear dentro del loop() las veces que el switch conectado a nuestra entrada es pulsado y por cuanto tiempo. Para ello tenemos que registrar los cambios de estado y los milisegundos que tomó este paso, mediante la función millis(). Dependiendo el tiempo oprimido, solo debemos modificar el primer byte de la memoria a un estado diferente al de ID_KNOW y resetear mediante la función soft_restart() el Arduino.

const int switchPin = ;                 // Usado para identifiar la entrada digital al Switch de Reset de Fabrica
int valueReset = 0;                     // Cablear resistencia 4,7k entre Gnd y entrada digital (switchPin)
int prevValueReset = 0;                 // El Switch se cablea entre 5V y la entrada digital (switchPin)
uint32_t prevMillisReset = millis();
 
void setup(){
  pinMode(switchPin, INPUT);
}
 
void loop(){
  prevValueReset = valueReset;
  valueReset = digitalRead(switchPin);
 
  if (valueReset == HIGH && prevValueReset == LOW) {                // Si oprimo el boton
    prevMillisReset = millis(); 
  }else if (valueReset == LOW && prevValueReset == HIGH) {          // Si suelto el boton 
    if(millis()-prevMillisReset>3000){                              // Si es mayor a 3 segundos Reset a Fabrica
      EEPROM.write(0, 0x00);   
    }                                                               // Reseteo el equipo
    soft_restart();
  }
}

El código completo se puede observar en las siguientes lineas donde restaría implementar los métodos de validación de los parámetros de red.

/*************************************************************************
  Configuracion de red via web con usuario/password y boton de reset
  Version: 0.5
  Fecha: 27/02/2018
  Mas informacion: https://pablo.sarubbi.com.ar
*************************************************************************/
#define WEBDUINO_FAVICON_DATA ""                        // Resta 198 byte al codigo por el Favicon del Webserver
#include <WebServer.h>                                  // https://github.com/sirleech/Webduino
#include <EEPROM.h>                                     // https://www.arduino.cc/en/Reference/EEPROM
#include <SoftReset.h>                                  // https://github.com/WickedDevice/SoftReset
#include <rBase64.h>                                    // https://github.com/boseji/rBASE64
 
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED};
byte ip[] = {192,168,1,2};
byte subnet[] = {255,255,0,0};
byte ip_gateway[] = {192,168,1,1};
byte ip_dns[] = {192,168,1,1};
char cUserPass[] = "admin:admin";
char cUserPass64[45] = "YWRtaW46YWRtaW4=";              // admin:admin en base64
 
const byte ID_KNOW = 0x92;                              // Usado para identificar si se grabaron datos en la EEPROM 
char buffer[100];
 
const int switchPin = 2;                                // Usado para identifiar la entrada digital al Switch de Reset de Fabrica
int valueReset = 0;                                     // Cablear resistencia 4,7k entre Gnd y entrada digital (switchPin)
int prevValueReset = 0;                                 // El Switch se cablea entre 5V y la entrada digital (switchPin)
uint32_t prevMillisReset = millis();
 
#define PREFIX ""
WebServer webserver(PREFIX, 80);
 
void defaultCmd(WebServer &client, WebServer::ConnectionType type, char *, bool){
  client.httpSuccess();
  if (type != WebServer::HEAD){
    client.print(F("Hola Mundo!"));
  }
}
 
void privateCmd(WebServer &client, WebServer::ConnectionType type, char *, bool){
 
  if (client.checkCredentials(cUserPass64)){          
    client.httpSuccess();
    if (type != WebServer::HEAD){
      if (type == WebServer::POST){
        bool repeat;
        char name[16];
        char value[16];
 
        do{
          repeat = client.readPOSTparam(name, 16, value, 16);
          switch (name[0]){
          case 'm':                                                 // Si es la mac address
            mac[(int)name[1] - 48]=strtoul(value, NULL, HEX);
            break;
          case 'i':                                                 // Si es la IP
            ip[(int)name[1] - 48]=strtoul(value, NULL, 10);
            break;
          case 's':                                                 // Si es la mascara de subred
            subnet[(int)name[1] - 48]=strtoul(value, NULL, 10);
            break;
          case 'g':                                                 // Si es el GATEWAY
            ip_gateway[(int)name[1] - 48]=strtoul(value, NULL, 10);
            break;
          case 'd':                                                 // Si es el DNS
            break; 
          case 'p':
            strcpy(cUserPass, "admin:");
            strcat(cUserPass, value);
            rbase64.encode(cUserPass).toCharArray(cUserPass64,45);
            break;  
          }
        } while (repeat);
 
        // Guardo los datos en la EEPROM
        EEPROM.write(0, ID_KNOW); 
        EEPROM.write(1, mac[0]); EEPROM.write(2, mac[1]); EEPROM.write(3, mac[2]); EEPROM.write(4, mac[3]); EEPROM.write(5, mac[4]); EEPROM.write(6, mac[5]); 
        EEPROM.write(7, ip[0]); EEPROM.write(8, ip[1]); EEPROM.write(9, ip[2]); EEPROM.write(10, ip[3]); 
        EEPROM.write(11, subnet[0]); EEPROM.write(12, subnet[1]); EEPROM.write(13, subnet[2]); EEPROM.write(14, subnet[3]); 
        EEPROM.write(15, ip_gateway[0]); EEPROM.write(16, ip_gateway[1]); EEPROM.write(17, ip_gateway[2]); EEPROM.write(18, ip_gateway[3]); 
        EEPROM.write(19, ip_dns[0]); EEPROM.write(20, ip_dns[1]); EEPROM.write(21, ip_dns[2]); EEPROM.write(22, ip_dns[3]);
        // Grabo el BASE64 de admin:password entre las posiciones de memoria 23 y 65
        for (int i=0; i < 43; i++){
          EEPROM.write(i+23, String(cUserPass64)[i]);
        }
 
        client.print("<meta http-equiv=\"refresh\" content=\"3; url=http://");
        client.print(ip[0],DEC);
        for (int i= 1; i < 4; i++){
          client.print(".");
          client.print(ip[i],DEC);
        }
        client.println("/index.html\" >");
        client.reset();
        delay(1);
        soft_restart();
      } else {
        client.println(F("<html><title>Arduino Ethernet WebSetup</title>"));
        client.println(F("<body marginwidth=\"0\" marginheight=\"0\" leftmargin=\"0\" style=\"margin: 0; padding: 0;\">"));
        client.println(F("<script>function hex2num (s_hex) {eval(\"var n_num=0X\" + s_hex);return n_num;}</script>"));
        client.println(F("<form method='post'>"));
        client.println(F("<table  border=\"0\" width=\"100%\" cellpadding=\"1\" style=\"font-family:Verdana;font-size:12px;\">"));
 
        client.println(F("\t<tr><td colspan=\"2\" style=\"background-color: #999999;\">Ethernet</td></tr>"));
 
        client.println(F("\t<tr><td>MAC:</td><td>"));
        client.println(F("\t\t<input type=\"hidden\" name=\"SBM\" value=\"1\">"));
        client.print(F("\t\t<input id=\"m0\" type=\"text\" size=\"2\" maxlength=\"2\" name=\"m0\" value=\"")); client.print(mac[0],HEX); client.println(F("\">:"));
        client.print(F("\t\t<input id=\"m1\" type=\"text\" size=\"2\" maxlength=\"2\" name=\"m1\" value=\"")); client.print(mac[1],HEX); client.println(F("\">:"));
        client.print(F("\t\t<input id=\"m2\" type=\"text\" size=\"2\" maxlength=\"2\" name=\"m2\" value=\"")); client.print(mac[2],HEX); client.println(F("\">:"));
        client.print(F("\t\t<input id=\"m3\" type=\"text\" size=\"2\" maxlength=\"2\" name=\"m3\" value=\"")); client.print(mac[3],HEX); client.println(F("\">:"));
        client.print(F("\t\t<input id=\"m4\" type=\"text\" size=\"2\" maxlength=\"2\" name=\"m4\" value=\"")); client.print(mac[4],HEX); client.println(F("\">:"));
        client.print(F("\t\t<input id=\"m5\" type=\"text\" size=\"2\" maxlength=\"2\" name=\"m5\" value=\"")); client.print(mac[5],HEX); client.println(F("\">"));
        client.println(F("\t</td></tr>"));
 
        client.println(F("\t<tr><td>IP:</td><td>"));
        client.print(F("\t\t<input type=\"text\" size=\"3\" maxlength=\"3\" id=\"i0\" name=\"i0\" value=\"")); client.print(ip[0],DEC); client.println(F("\">."));
        client.print(F("\t\t<input type=\"text\" size=\"3\" maxlength=\"3\" id=\"i1\" name=\"i1\" value=\"")); client.print(ip[1],DEC); client.println(F("\">."));
        client.print(F("\t\t<input type=\"text\" size=\"3\" maxlength=\"3\" id=\"i2\" name=\"i2\" value=\"")); client.print(ip[2],DEC); client.println(F("\">."));
        client.print(F("\t\t<input type=\"text\" size=\"3\" maxlength=\"3\" id=\"i3\" name=\"i3\" value=\"")); client.print(ip[3],DEC); client.println(F("\">"));
        client.println(F("\t</td></tr>"));
 
        client.println(F("\t<tr><td>SUBNET:</td><td>"));
        client.print(F("\t\t<input type=\"text\" size=\"3\" maxlength=\"3\" id=\"s0\" name=\"s0\" value=\"")); client.print(subnet[0],DEC); client.println(F("\">."));    
        client.print(F("\t\t<input type=\"text\" size=\"3\" maxlength=\"3\" id=\"s1\" name=\"s1\" value=\"")); client.print(subnet[1],DEC); client.println(F("\">."));       
        client.print(F("\t\t<input type=\"text\" size=\"3\" maxlength=\"3\" id=\"s2\" name=\"s2\" value=\"")); client.print(subnet[2],DEC); client.println(F("\">."));    
        client.print(F("\t\t<input type=\"text\" size=\"3\" maxlength=\"3\" id=\"s3\" name=\"s3\" value=\"")); client.print(subnet[3],DEC); client.println(F("\">"));
        client.println(F("\t</td></tr>"));
 
        client.println(F("\t<tr><td>GW:</td><td>"));
        client.print(F("\t\t<input type=\"text\" size=\"3\" maxlength=\"3\" id=\"g0\" name=\"g0\" value=\"")); client.print(ip_gateway[0],DEC); client.println(F("\">."));
        client.print(F("\t\t<input type=\"text\" size=\"3\" maxlength=\"3\" id=\"g1\" name=\"g1\" value=\"")); client.print(ip_gateway[1],DEC); client.println(F("\">."));
        client.print(F("\t\t<input type=\"text\" size=\"3\" maxlength=\"3\" id=\"g2\" name=\"g2\" value=\"")); client.print(ip_gateway[2],DEC); client.println(F("\">."));
        client.print(F("\t\t<input type=\"text\" size=\"3\" maxlength=\"3\" id=\"g3\" name=\"g3\" value=\"")); client.print(ip_gateway[3],DEC); client.println(F("\">"));
        client.println(F("\t</td></tr>"));
 
        client.println(F("\t<tr><td>DNS:</td><td>"));
        client.print(F("\t\t<input type=\"text\" size=\"3\" maxlength=\"3\" id=\"d0\" name=\"d0\" value=\"")); client.print(ip_dns[0],DEC); client.println(F("\">."));
        client.print(F("\t\t<input type=\"text\" size=\"3\" maxlength=\"3\" id=\"d1\" name=\"d1\" value=\"")); client.print(ip_dns[1],DEC); client.println(F("\">."));
        client.print(F("\t\t<input type=\"text\" size=\"3\" maxlength=\"3\" id=\"d2\" name=\"d2\" value=\"")); client.print(ip_dns[2],DEC); client.println(F("\">."));
        client.print(F("\t\t<input type=\"text\" size=\"3\" maxlength=\"3\" id=\"d3\" name=\"d3\" value=\"")); client.print(ip_dns[3],DEC); client.println(F("\">"));
        client.println(F("\t</td></tr>"));
 
        client.println(F("\t<tr><td colspan=\"2\" style=\"background-color: #999999;\">Usuarios</td></tr>"));
 
        String sUserPass = rbase64.decode(String(cUserPass64));
        String sPass = sUserPass.substring(6,String(cUserPass).length());
 
        client.println(F("\t<tr><td>USUARIO:</td><td>admin</td></tr>"));
        client.println(F("\t<tr><td>CONTRASEÑA:</td><td>"));
        client.print(F("\t\t<input type=\"password\" size=\"16\" maxlength=\"16\" id=\"password\" name=\"password\" value=\"")); client.print(sPass); client.println(F("\">"));
        client.println(F("\t</td></tr>"));
 
        client.println(F("\t<tr><td>&nbsp;</td></tr>"));
 
        client.println(F("<tr><td colspan=\"2\">"));
        client.println(F("\t\t<input id=\"button1\" type=\"submit\" value=\"GUARDAR\" />"));
        client.println(F("\t</td></tr>"));
        client.print(F("</table>\n</form>\n</body>\n</html>")); 
      }
    }
  }else{
    client.httpUnauthorized();
  }
}
 
void setup(){
  pinMode(switchPin, INPUT);
 
  ShieldSetup (); 
  webserver.setDefaultCommand(&defaultCmd);
  webserver.addCommand("index.html", &defaultCmd);
  webserver.addCommand("setup.html", &privateCmd);
  webserver.begin();
}
 
void ShieldSetup(){
  //  Serial.begin(9600);
  int idcheck = EEPROM.read(0);
 
  if (idcheck == ID_KNOW){
    mac[0] = EEPROM.read(1); mac[1] = EEPROM.read(2); mac[2] = EEPROM.read(3); mac[3] = EEPROM.read(4); mac[4] = EEPROM.read(5); mac[5] = EEPROM.read(6); 
    ip[0] = EEPROM.read(7); ip[1] = EEPROM.read(8); ip[2] = EEPROM.read(9); ip[3] = EEPROM.read(10); 
    subnet[0] = EEPROM.read(11); subnet[1] = EEPROM.read(12); subnet[2] = EEPROM.read(13); subnet[3] = EEPROM.read(14); 
    ip_gateway[0] = EEPROM.read(15); ip_gateway[1] = EEPROM.read(16); ip_gateway[2] = EEPROM.read(17); ip_gateway[3] = EEPROM.read(18); 
    ip_dns[0] = EEPROM.read(19); ip_dns[1] = EEPROM.read(20); ip_dns[2] = EEPROM.read(21); ip_dns[3] = EEPROM.read(22); 
    for (int i=0;i<43;i++){
      cUserPass64[i] = EEPROM.read(i+23); 
    }
  }
  Ethernet.begin(mac, ip, ip_dns, ip_gateway, subnet);
}
 
void loop(){
  char buff[64];
  int len = 64;
 
  webserver.processConnection(buff, &len);  
 
  // RESET A FABRICA CON SWITCH EN PIN 2
  prevValueReset = valueReset;
  valueReset = digitalRead(switchPin);
 
  if (valueReset == HIGH && prevValueReset == LOW) {                // Si oprimo el boton
    prevMillisReset = millis(); 
  }else if (valueReset == LOW && prevValueReset == HIGH) {          // Si suelto el boton 
    if(millis()-prevMillisReset>3000){                              // Si es mayor a 3 segundos Reset a Fabrica
      EEPROM.write(0, 0x00);   
    }                                                               // Reseteo el equipo
    soft_restart();
  }
}

Apoyándonos en el post anterior como se chequea el sensor de temperatura y humedad DHT22 desde una placa Arduino, decidimos agregarle un poco de complejidad al proyecto e incluir este sensor a nuestro sistema de monitoreo Icinga (ex Nagios), quien sensará la placa mediante el protocolo snmp. Para ello debemos incluir nuevas librerías que nos facilitarán el manejo de este protocolo.

Agentuino

Agentuino es una librería ligera de SNMP que implementa la versión 1 del protocolo. Actualmente la base del desarrollo actual es sincrónico, esto significa que el sistema no ejecutará ningún otro código hasta que procese la solicitud y envíe la respuesta al solicitante. A continuación resaltamos las líneas de mayor relevancia en el proyecto:

    SNMP_PDU pdu;	// Define la clase
 
    const char snmp_temperature[]     PROGMEM     = "1.3.6.1.3.2016.5.1.0";     		// OID Custom definido para Temperatua 
    ...
    void pduReceived()
      ...
      else if ( strcmp_P(oid, snmp_temperature ) == 0 ) {					// Si es el OID que nos interesa
      ...
	} else { 										// viene del if ( pdu.type == SNMP_PDU_SET ) 
	  status = pdu.VALUE.encode(SNMP_SYNTAX_OCTETS, dtostrf(temperature,6,2,result));	// Retorna el valor temperature casteado de Float
 
 
    void setup(){
      api_status = Agentuino.begin();								// Inicializa el objeto
      if ( api_status == SNMP_API_STAT_SUCCESS ) {
	Agentuino.onPduReceive(pduReceived);							// Llama a pduReceived()
      ...
 
    void loop(){ 
      Agentuino.listen();									// Espera solicitudes de SNMP entrantes
    }

Limitaciones
Las limitaciones de Agentuino están dadas por la cantidad de SRAM disponible.

  • Longitud máxima del Nombre de comunidad (Get / Set): 20 bytes
  • Longitud máxima del identificador de objetos: 64 bytes
  • Longitud máxima del valor: 64 bytes
  • Longitud máxima del paquete: 153 bytes

Requerimientos
Los desarrolladores especifican las siguientes dependencias de software:

  • Ethernet Library 0019 o superior
  • SPI Library 0019 o superior
  • Streaming Library
  • Flash Library
  • MemoryFree Library

Referencias de la librería Agentuino
https://code.google.com/archive/p/agentuino/
https://en.wikipedia.org/wiki/Simple_Network_Management_Protocol
https://github.com/1sw/Agentuino
https://github.com/PaulStoffregen/SPI
https://github.com/geneReeves/ArduinoStreaming
https://github.com/mikalhart/Flash
https://github.com/mpflaga/Arduino-MemoryFree

DHT
Esta librería nos permite realizar de manera sencilla la lectura de los sensores de temepratura y humedad DHT11 y DHT22 sin tener que preocuparnos por el protocolo de comunicación entre el Arduino y los sensores. Las siguientes lineas definen los pasos mas importantes para obtener una lectura del sensor:

    #define DHTPIN 2				// Nro. de pin donde se encuentra conectado el sensor
    #define DHTTYPE DHT22			// Modelo de sensor, puede ser DHT11, DHT21 o DHT22
    DHT dht(DHTPIN, DHTTYPE);			// Definicion del sensor
 
    dht.begin();				// Inicializacion del sensor
 
    float humidity = dht.readHumidity();	// Lectura de humedad
    float temperature = dht.readTemperature();	// Lectura de Temperatura en grados Celsius, con "true" como parametro del metodo readTemperature se obtiene el valor en grados Fahrenheit

Referencias de la librería DHT
http://www.naylampmechatronics.com/blog/40_Tutorial-sensor-de-temperatura-y-humedad-DHT1.html
https://github.com/adafruit/DHT-sensor-library

El código completo

/*************************************************
  Como sensar temperatura y humedad con un 
  Arduino Uno y el sensor DHT22 para enviando 
  los datos por snmp.
  Version: 0.1
  Fecha: 08/02/2018
  Mas informacion: https://pablo.sarubbi.com.ar
**************************************************/
 
 
#include <Ethernet.h>                                          // Libreria para manejo de pila de TCP/IP
#include <SPI.h>                                               // Requerido por Libreria Agentuino
#include <Streaming.h>                                         // Requerido por Libreria Agentuino
#include <Flash.h>                                             // Requerido por Libreria Agentuino
#include <MemoryFree.h>                                        // Requerido por Libreria Agentuino
#include <Agentuino.h>                                         // Libreria que implementa el protocolo SMNPv1 
#include <DHT.h>                                               // Libreria que implementa de manera sencilla la lectura del sensor de temperatura/humedad
 
static byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };    //Mac Address for Arduino Ethernet Shield
static byte ip[] = { 192, 168, 1, 100 };
static byte ip_dns[] = { 192, 168, 1, 1 };
static byte ip_gateway[] = { 192, 168, 1, 1};
static byte subnet[] = { 255, 255, 0, 0 };
 
//Constants
#define DHTPIN 2                                              // Nro. de pin donde se encuentra conectado el sensor
#define DHTTYPE DHT22                                         // Modelo de sensor, puede ser DHT11, DHT21 o DHT22
DHT dht(DHTPIN, DHTTYPE);                                     // Inicializacion del sensor 
 
//Variables
float humidity=0;
float temperature=0;
char result[8];
 
const char sysDescr[] PROGMEM      = "1.3.6.1.2.1.1.1.0";  // System Description
const char sysContact[] PROGMEM    = "1.3.6.1.2.1.1.4.0";  // System Contact
const char sysName[] PROGMEM       = "1.3.6.1.2.1.1.5.0";  // System Name
const char sysLocation[] PROGMEM   = "1.3.6.1.2.1.1.6.0";  // System Location
const char sysServices[] PROGMEM   = "1.3.6.1.2.1.1.7.0";  // System Services
 
//My Custom OIDs
const char snmp_temperature[]     PROGMEM     = "1.3.6.1.3.2016.5.1.0";     // Temperatua en grados Celsius
const char snmp_humidity[]        PROGMEM     = "1.3.6.1.3.2016.5.1.1";     // Humedad porcentual
 
// RFC1213 local values
static char locDescr[]              = "SNMP - Arduino - DHT22";             // read-only (static)
static char locContact[50]          = "Contacto";                           // should be stored/read from EEPROM - read/write (not done for simplicity)
static char locName[20]             = "Nombre";                             // should be stored/read from EEPROM - read/write (not done for simplicity)
static char locLocation[20]         = "BA - Argentina";                     // should be stored/read from EEPROM - read/write (not done for simplicity)
static int32_t locServices          = 2;                                    // read-only (static)
 
uint32_t prevMillis = millis();
char oid[SNMP_MAX_OID_LEN];
SNMP_API_STAT_CODES api_status;
SNMP_ERR_CODES status;
//EthernetServer server(80);
 
void pduReceived()
{
  SNMP_PDU pdu;
  api_status = Agentuino.requestPdu(&pdu);
  //
  if ((pdu.type == SNMP_PDU_GET || pdu.type == SNMP_PDU_GET_NEXT || pdu.type == SNMP_PDU_SET)
    && pdu.error == SNMP_ERR_NO_ERROR && api_status == SNMP_API_STAT_SUCCESS ) {
    //
    pdu.OID.toString(oid);
 
    if ( strcmp_P(oid, sysDescr ) == 0 ) {
      // handle sysDescr (set/get) requests
      if ( pdu.type == SNMP_PDU_SET ) {
        // response packet from set-request - object is read-only
        pdu.type = SNMP_PDU_RESPONSE;
        pdu.error = SNMP_ERR_READ_ONLY;
      } else {
        // response packet from get-request - locDescr
        status = pdu.VALUE.encode(SNMP_SYNTAX_OCTETS, locDescr);
        pdu.type = SNMP_PDU_RESPONSE;
        pdu.error = status;
      } 
    } else if ( strcmp_P(oid, sysName ) == 0 ) {
      // handle sysName (set/get) requests
      if ( pdu.type == SNMP_PDU_SET ) {
        // response packet from set-request - object is read/write
        status = pdu.VALUE.decode(locName, strlen(locName)); 
        pdu.type = SNMP_PDU_RESPONSE;
        pdu.error = status;
      } else {
        // response packet from get-request - locName
        status = pdu.VALUE.encode(SNMP_SYNTAX_OCTETS, locName);
        pdu.type = SNMP_PDU_RESPONSE;
        pdu.error = status;
      }
      //
    } else if ( strcmp_P(oid, sysContact ) == 0 ) {
      // handle sysContact (set/get) requests
      if ( pdu.type == SNMP_PDU_SET ) {
        // response packet from set-request - object is read/write
        status = pdu.VALUE.decode(locContact, strlen(locContact)); 
        pdu.type = SNMP_PDU_RESPONSE;
        pdu.error = status;
      } else {
        // response packet from get-request - locContact
        status = pdu.VALUE.encode(SNMP_SYNTAX_OCTETS, locContact);
        pdu.type = SNMP_PDU_RESPONSE;
        pdu.error = status;
      }
      //
    } else if ( strcmp_P(oid, sysLocation ) == 0 ) {
      // handle sysLocation (set/get) requests
      if ( pdu.type == SNMP_PDU_SET ) {
        // response packet from set-request - object is read/write
        status = pdu.VALUE.decode(locLocation, strlen(locLocation)); 
        pdu.type = SNMP_PDU_RESPONSE;
        pdu.error = status;
      } else {
        // response packet from get-request - locLocation
        status = pdu.VALUE.encode(SNMP_SYNTAX_OCTETS, locLocation);
        pdu.type = SNMP_PDU_RESPONSE;
        pdu.error = status;
      }
      //
    } else if ( strcmp_P(oid, sysServices) == 0 ) {
      // handle sysServices (set/get) requests
      if ( pdu.type == SNMP_PDU_SET ) {
        // response packet from set-request - object is read-only
        pdu.type = SNMP_PDU_RESPONSE;
        pdu.error = SNMP_ERR_READ_ONLY;
      } else {
        // response packet from get-request - locServices
        status = pdu.VALUE.encode(SNMP_SYNTAX_INT, locServices);
        pdu.type = SNMP_PDU_RESPONSE;
        pdu.error = status;
      }
      //
    }
    else if ( strcmp_P(oid, snmp_temperature ) == 0 ) 
    {
      // handle sysName (set/get) requests
      if ( pdu.type == SNMP_PDU_SET ) 
      {      
        // response packet from set-request - object is read-only
        pdu.type = SNMP_PDU_RESPONSE;
        pdu.error = SNMP_ERR_READ_ONLY;
      } 
      else 
      {
        status = pdu.VALUE.encode(SNMP_SYNTAX_OCTETS, dtostrf(temperature,6,2,result));
        pdu.type = SNMP_PDU_RESPONSE;
        pdu.error = status;
      }
    } 
        else if ( strcmp_P(oid, snmp_humidity ) == 0 ) 
    {
      // handle sysName (set/get) requests
      if ( pdu.type == SNMP_PDU_SET ) 
      {      
        // response packet from set-request - object is read-only
        pdu.type = SNMP_PDU_RESPONSE;
        pdu.error = SNMP_ERR_READ_ONLY;
      } 
      else 
      {
        status = pdu.VALUE.encode(SNMP_SYNTAX_OCTETS, dtostrf(humidity,6,2,result));
        pdu.type = SNMP_PDU_RESPONSE;
        pdu.error = status;
      }
    }   
    else {
      // oid does not exist
      // response packet - object not found
      pdu.type = SNMP_PDU_RESPONSE;
      pdu.error = SNMP_ERR_NO_SUCH_NAME;
    }
    Agentuino.responsePdu(&pdu);
  }
 
  Agentuino.freePdu(&pdu);
 
}
 
void setup()
{
  dht.begin();
 
  Ethernet.begin(mac, ip, ip_dns, ip_gateway, subnet);    // Se inicializa Ethernet Shield
  api_status = Agentuino.begin();                         // Begin Snmp agent on Ethernet shield
 
  if ( api_status == SNMP_API_STAT_SUCCESS ) {
    Agentuino.onPduReceive(pduReceived);
    delay(10);
    return;
  }
  delay(10);
}
 
void loop()
{ 
  Agentuino.listen();                                      //Listen/Handle for incoming SNMP requests
  if(millis()-prevMillis>2000){
    humidity = dht.readHumidity();
    temperature = dht.readTemperature();
    prevMillis = millis();
  }
}

El sistema funcionando se puede consultar desde una consola con el comando snmpget

pablo@laptop:~# watch -n 5 snmpget -v 1 -c public 192.168.1.100 1.3.6.1.3.2016.5.1.0
Every 1.0s: snmpget -v 1 -c public 10.10.0.75 1.3.6.1.3.2016.5.1.0           Thu Feb  8 13:40:08 2018
 
iso.3.6.1.3.2016.5.1.0 = STRING: " 28.60"

Configurando Icinga o Nagios
Ene el directorio /etc/nagios-plugins/config/ vamos a crear un nuevo comando llamado check_snmp_temp el cual ejecutará una consulta similar a la que hicimos con el comando snmpget, al que le adicionaremos dos argumentos que serán los valores aceptados para notificaciones por Warning y Error.

# 'snmp_temp' command definition
define command{
        command_name    check_snmp_temp
        command_line    /usr/lib/nagios/plugins/check_snmp -H '$HOSTADDRESS$' -C 'public' -o 1.3.6.1.3.2016.5.1.0 -w$ARG1$ -c$ARG2$ -l temp
}

Una vez definido el comando check_snmp_temp, agregamos un host MonitorDeTemperatura con este servicio de la siguiente manera:

define host{
        use                     generic-host
        host_name               MonitorDeTemperatura
        alias                   MonitorDeTemperatura
        address                 192.168.1.100
        notification_period     24x7
}
 
define service{
        use                     generic-service
        host_name               MonitorDeTemperatura
        notifications_enabled   0
        service_description     Servicio de Temperatura
        check_command           check_snmp_temp!26!30
}

Estos parámetros hacen que el Icinga de un alerta por Warning cuando el monitor de temperatura pase los 26 grados centrígrados, y un aviso de Error cuando se superen los 30 grados centrígrados.

Por ser nuestro primer proyecto con un Arduino, vamos a ser lo mas detallado que podamos, así que empezamos por descargar el IDE de https://www.arduino.cc/en/Main/Software para continuar con los pasos de instalación que no son mas que descomprimir y correr el script ./install.sh . Como último paso debemos agregar nuestro usuario al grupo dialout con “sudo usermod -a -G dialout ” y reiniciar la sesión para que tome los cambios. Este procedimiento agrega la aplicación Arduino IDE en nuestra barra de inicio.

Los principales comandos se ejecutan desde la barra de menú principal, Verificar el código (con el ícono de Tilde) y enviar el programa al Arduino (con el ícono de la flecha). Ambos se pueden observar en la siguiente imagen:

En el menú superior, dentro de Herramientas -> Placa definimos el modelo de Arduino que utilizamos, en nuestro caso el Arduino GenuinoUno

También en el menú superior, dentro de Herramientas -> Puerto se define en donde está conectada la placa, en nuestro caso al /dev/ttyACM0

y por último, otro menú a destacar está en el menú superior, Programa -> Incluir Libreria -> Añadir Libreria .ZIP, desde donde importaremos las librerías que se llaman desde los #include desgargados en formato .zip en su mayoría desde github.

La Placa Arduino Uno

Arduino es una plataforma de electrónica open source facil de usar compuesta por un microcontrolador y una serie de puertos de entrada y salida, y es por ser de código abierto que encontraremos varios formatos, precios y colores basados en este proyecto.

Para dar nuestros primeros pasos el Arduino Uno tiene suficiente cantidad de entradas/salidas y muchas referencias en internet como para descargar código de pruebas.

El sensor

Los DHT11 y DHT22 son sensores digitales de temperatura y humedad que utilizan un termistor para medir la temperatura del aire circundante y un sensor capacitivo para obtener la humedad. Estos datos se transmiten sobre un único pin, el segundo de izquierda a derecha visto de frente. Una desventaja que tiene este componente es la velocidad de lectura, donde se recomienda obtener un dato pasado los 2 segundos sobre la lectura anterior. Para solucionar esta restricción tenemos el siguiente fragmento de nuestro código dentro del loop() del Arduino.

  if(millis()-prevMillis>2000){
    humidity = dht.readHumidity();
    temperature = dht.readTemperature();
    prevMillis = millis();
  }

El sensor DHT11 trabaja con un rango de medición de temperatura de 0 a 50 grados centígrados con una precisión de +/-2.0 y un rango de humedad de 20% a 90%RH con precisión de 4%. El sensor DHT22 tiene un rango de medición de temperatura de -40 a 80 grados centígrados y un rango de humedad de 0 a 100%RH con precisión de 2%RH.

El conexionado

El valor de la resistencia puede estar entre 4k7 y 10k ohms

Comparativa entre sensores DHT11 y DHT22
http://www.kandrsmith.org/RJS/Misc/Hygrometers/calib_dht22_dht11_sht71.html

Este proyecto involucra manejo del protocolos de red y obviamente procesos de lecturas sobre el sensor de humedad y temperatura. Para ello vamos a necesitar incluir algunas librerías.

Ethernet

La librería Ethernet es la usada para manejar el Ethernet Shield que implementa la pila de protocolos TCP/IP y dentro de Arduino se implementan los protocolos en la capa de aplicación.

Para manejar el Ethernet Shield deberemos conocer todos los métodos que nos ofrece la librería Ethernet y así poder usarla.

Referencias de librería Ethernet:
http://arduino.cc/en/Reference/Ethernet
https://aprendiendoarduino.wordpress.com/2016/07/05/libreria-ethernet/

Ethernet.begin(mac, ip, ip_dns, ip_gateway, subnet) // Inicializa la librería Ethernet (Constructor)

DHT
Esta librería nos permite realizar de manera sencilla la lectura de los sensores de temepratura y humedad DHT11 y DHT22 sin tener que preocuparnos por el protocolo de comunicación entre el Arduino y los sensores. Las siguientes lineas definen los pasos mas importantes para obtener una lectura del sensor:

    #define DHTPIN 2				// Nro. de pin donde se encuentra conectado el sensor
    #define DHTTYPE DHT22			// Modelo de sensor, puede ser DHT11, DHT21 o DHT22
    DHT dht(DHTPIN, DHTTYPE);			// Definicion del sensor
 
    dht.begin();				// Inicializacion del sensor
 
    float humidity = dht.readHumidity();	// Lectura de humedad
    float temperature = dht.readTemperature();	// Lectura de Temperatura en grados Celsius, con "true" como parametro del metodo readTemperature se obtiene el valor en grados Fahrenheit

Referencias de la librería DHT
http://www.naylampmechatronics.com/blog/40_Tutorial-sensor-de-temperatura-y-humedad-DHT1.html
https://github.com/adafruit/DHT-sensor-library

El código completo

/*************************************************
  Como sensar temperatura y humedad con un 
  Arduino Uno y el sensor DHT22 para visualizar
  los datos por la web.
  Version: 0.1
  Fecha: 08/02/2018
  Mas informacion: https://pablo.sarubbi.com.ar
**************************************************/
 
#include <Ethernet.h>                                          // Libreria para manejo de pila de TCP/IP
#include <DHT.h>                                               // Libreria que implementa de manera sencilla la lectura del sensor de temperatura/humedad
 
static byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };    //Mac Address for Arduino Ethernet Shield
static byte ip[] = { 192, 168, 1, 100 };
static byte ip_dns[] = { 192, 168, 1, 1 };
static byte ip_gateway[] = { 192, 168, 1, 1};
static byte subnet[] = { 255, 255, 0, 0 };
 
//Constants
#define DHTPIN 2                                              // Nro. de pin donde se encuentra conectado el sensor
#define DHTTYPE DHT22                                         // Modelo de sensor, puede ser DHT11, DHT21 o DHT22
DHT dht(DHTPIN, DHTTYPE);                                     // Inicializacion del sensor 
 
//Variables
float humidity=0;
float temperature=0;
uint32_t prevMillis = millis();
 
EthernetServer server(80);
 
void setup()
{
  dht.begin();
  Ethernet.begin(mac, ip, ip_dns, ip_gateway, subnet);    // Se inicializa Ethernet Shield
  server.begin();                                         // Se inicializa el WebServer
}
 
void loop()
{ 
 
  if(millis()-prevMillis>2000){
    humidity = dht.readHumidity();
    temperature = dht.readTemperature();
    prevMillis = millis();
  }
 
  // Servicio Web para lectura online de Temperatura y Humedad
  EthernetClient client = server.available();
  if (client) {
    boolean currentLineIsBlank = true;
    while (client.connected()) {
      if (client.available()) {
          client.println("HTTP/1.1 200 OK");
          client.println("Content-Type: text/html");
          client.println("Connection: close");
          client.println("Refresh: 5"); // refresh the page automatically every 5 sec
          client.println();
          client.println("<!DOCTYPE HTML>");
          client.println("<html>");
          client.println("<head>");
          client.println("\t<title>Arduino Uno + DHT22</title>");
          client.println("</head>");
          client.println("<body>");
          client.print("\t<b>Temperatura:</b> "); client.println(temperature);
          client.print("\t<br /><b>Humedad:</b> "); client.println(humidity);
          client.println("</body>");
          client.print("</html>");
          break;
      }
    }
  }
  delay(1);
  client.stop(); 
}

Accediendo desde un browser a http://192.168.1.100 podemos ver las lecturas como se muestra en la siguiente captura

Fail2ban es una herramienta que monitorea los logs y se activa (ejecutando una acción) cuando detecta un patrón sospechoso previamente definido (filtro).

Para instalar Fail2ban y sus dependencias en Ubuntu solo debemos ejecutar en una terminal el siguiente comando:

sudo apt-get install fail2ban

El archivo de configuraci&ocaute;n principal está es /etc/fail2ban/jail.conf y contiene de conjunción de los patrones a buscar y acciones a ejecutar por cada servicio monitoreado. Un ejemplo que viene configurado por default en el archivo es la sección [ssh] donde podemos ver habilitado el filtro sshd que buscará sobre el archivo log /var/log/auth.log la cantidad de maxretry definidas (en este caso 6).

[ssh]
 
enabled  = true
port     = ssh
filter   = sshd
logpath  = /var/log/auth.log
maxretry = 6

La entrada “filter = sshd” está definida en el archivo /etc/fail2ban/filter.d/sshd que básicamente se compone por una expresión regular que tratará de encajar sobre el log pasado como parámetro.

Debemos reiniciar el servicio para que los cambios tengan efecto:

sudo service fail2ban restart

Pero esta regla solo generará una entrada en el log /var/log/fail2ban.log con el siguiente aviso.

2018-01-24 12:30:59,401 fail2ban.actions: WARNING [ssh] Ban 10.10.10.10

Si queremos ir un paso adelante, podemos construir reglas reactivas que bloqueen la IP del host que está intentando conectarse a nuestro server. Para ello vamos a modificar un poco la sección [ssh-iptables-ipset4] en primer lugar habilitando dicha regla, luego apuntando el logpath al archivo correcto y para finalizar definiendo una acción a ejecutar la cual bloqueará la IP en cuestión mediante el comando definido en iptables-ipset-proto4.

[ssh-iptables-ipset4]
 
enabled  = true
port     = ssh
filter   = sshd
banaction = iptables-ipset-proto4
logpath  = /var/log/auth.log
maxretry = 6

Donde ahora tenemos establecido el parámetro banaction que se define en el archivo /etc/fail2ban/action.d/iptables-ipset-proto4.conf que modifica las reglas de nuestro firewall mediante iptables y ipset*

* Si no tenemos ipset instalado, ejecutar sudo apt-get install ipset

Si ahora intentamos un acceso desde nuestro HOST de prueba 10.10.10.10 no solo veremos en el log de fail2ban la siguiente linea:

2018-01-24 12:39:33,325 fail2ban.actions: WARNING [ssh-iptables-ipset4] Ban 10.10.10.10

sino que además en las reglas de iptables tendremos una entrada como la siguiente:

REJECT     tcp  --  0.0.0.0/0            0.0.0.0/0            multiport dports 22 match-set fail2ban-ssh-iptables-ipset4 src reject-with icmp-port-unreachable

donde el set de IPs a bloquear ahora incluye la 10.10.10.10

# ipset list fail2ban-ssh-iptables-ipset4
Name: fail2ban-ssh-iptables-ipset4
Type: hash:ip
Revision: 2
Header: family inet hashsize 1024 maxelem 65536
Size in memory: 16520
References: 1
Members:
10.10.10.10

Podemos verificar el funcionamiento de las reglas de firewall intentando conectarnos nuevamente.

# ssh pablo@10.10.10.1
ssh: connect to host 10.10.10.1 port 22: Connection refused

Las secciones en el archivo /etc/fail2ban/jail.conf otros parámetros que se pueden definir.

bantime = 600 # Tiempo de baneado en segundos.
destemail = usuario@ejemplo.com # Destinatario de correo para aviso de visitante bloqueado

Probar una regla
Existe una forma sencilla de probar las expresiones regulares de nuevas reglas sobre un archivo log para verificar si aplica como nosotros queremos, sin que este afecte al sistema.

# fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf 
 
Running tests
=============
 
Use   failregex file : /etc/fail2ban/filter.d/sshd.conf
Use         log file : /var/log/auth.log
 
 
Results
=======
 
Failregex: 13 total
|-  #) [# of hits] regular expression
|   3) [11] ^\s*(<[^.]+\.[^.]+>)?\s*(?:\S+ )?(?:kernel: \[\d+\.\d+\] )?(?:@vserver_\S+ )?(?:(?:\[\d+\])?:\s+[\[\(]?sshd(?:\(\S+\))?[\]\)]?:?|[\[\(]?sshd(?:\(\S+\))?[\]\)]?:?(?:\[\d+\])?:?)?\s(?:\[ID \d+ \S+\])?\s*Failed \S+ for .*? from <HOST>(?: port \d*)?(?: ssh\d*)?(: (ruser .*|(\S+ ID \S+ \(serial \d+\) CA )?\S+ (?:[\da-f]{2}:){15}[\da-f]{2}(, client user ".*", client host ".*")?))?\s*$
|   5) [2] ^\s*(<[^.]+\.[^.]+>)?\s*(?:\S+ )?(?:kernel: \[\d+\.\d+\] )?(?:@vserver_\S+ )?(?:(?:\[\d+\])?:\s+[\[\(]?sshd(?:\(\S+\))?[\]\)]?:?|[\[\(]?sshd(?:\(\S+\))?[\]\)]?:?(?:\[\d+\])?:?)?\s(?:\[ID \d+ \S+\])?\s*[iI](?:llegal|nvalid) user .* from <HOST>\s*$
`-
 
Ignoreregex: 0 total
 
Date template hits:
|- [# of hits] date format
|  [888] MONTH Day Hour:Minute:Second
`-
 
Lines: 888 lines, 0 ignored, 13 matched, 875 missed
Missed line(s):: too many to print.  Use --print-all-missed to print all 875 lines

Algunos filtros mas

en /etc/fail2ban/filter.d/

# Fail2Ban configuration file qmail-auth.conf
# Author: Efra - Pablo
# Version 0.1
#
# 2018-01-24T10:02:17.562375-03:00 vps smtp_auth: smtp_auth: FAILED: support - password incorrect from (null)@walkerj2351.example.com [91.200.12.197]
[Definition]
failregex = (.* smtp_auth: FAILED: .* password incorrect from .*) \[<HOST>\]
ignoreregex =

en /etc/fail2ban/jail.conf

[qmail-auth]
enabled = true
filter  = qmail-auth
action  = iptables-allports[name=qmail-auth]
logpath  = /usr/local/psa/var/log/maillog

en /etc/fail2ban/filter.d/

# Fail2Ban configuration file wordpress-login.conf
# Author: Efra - Pablo
# Version 0.1
# Bloquea accesos al administrador de WP
#
# 91.200.12.49 - - [26/Apr/2017:11:42:02 -0300] "POST /wp-login.php HTTP/1.1" 200 3157 "https://pablo.sarubbi.com.ar/wp-login.php" "Mozilla/4.0 (compatible; MSIE 9.0; Windows NT 6.1; 125LA; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022)"
[Definition]
failregex = ^<HOST> -.*"(GET|POST).*\/wp-login.php HTTP\/.*$
ignoreregex =

en /etc/fail2ban/jail.conf

[wordpress-login]
enabled  = true
filter   = wordpress-login
action   = iptables-multiport[name=wordpressLogin, port="http,https"]
logpath  = /var/www/vhosts/*/statistics/logs/access_log
bantime  = 172800
maxretry = 5
findtime = 60

en /etc/fail2ban/filter.d/

# Fail2Ban configuration file wordpress-uploads.conf 
# Author: Efra - Pablo
# Version 0.1
# Bloquea accesos a archivos .php en directorio uploads de WP
# 98.130.0.199 - - [25/Apr/2017:07:25:14 -0300] "POST /wp-content/uploads/2011/09/footer92.php HTTP/1.0" 200 255 "https://pablo.sarubbi.com.ar/wp-content/uploads/2011/09/footer92.php" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.48 Safari/537.36"
[Definition]
failregex = ^<HOST> -.*"(GET|POST).*\/uploads\/.*\.php HTTP\/.*$
ignoreregex =

en /etc/fail2ban/jail.conf

[wordpress-uploads]
enabled  = true
filter   = wordpress-uploads
action   = iptables-multiport[name=wordpressUploads, port="http,https"]
logpath  = /var/www/vhosts/*/statistics/logs/access_log
bantime  = 172800
maxretry = 1

en /etc/fail2ban/filter.d/

# Fail2Ban configuration file  horde-login.conf 
# Author: Efra - Pablo
# Version 0.1
# Bloquea accesos al webmail Horde
# Jan 24 14:34:31 HORDE [error] [imp] FAILED LOGIN for zaraza@sarubbi.com.ar [139.162.9.175] to {localhost:143 [imap/notls]} [pid 21241 on line 139 of "/usr/share/psa-horde/imp/lib/Auth/imp.php"]
[Definition]
failregex = (.* FAILED LOGIN for .*) \[<HOST>\] to .*$
ignoreregex =

en /etc/fail2ban/jail.conf

[horde-login]
enabled  = true
filter   = horde-login
action   = iptables-multiport[name=horde-login, port="http,https"]
logpath  = /var/log/psa-horde/psa-horde.log
bantime  = 7200
maxretry = 5
findtime = 1200
Stop SOPA