Un blog mas

Bitácora de vuelo

En primer lugar debemos instalar y configurar el cliente msmtp

apt install msmtp

Creamos un archivo /etc/msmtprc y pegamos la siguiente configuración:

defaults
tls on
account default
host smtp.midominio.com
from fail2ban-noreply@midominio.com

Tomando como base los archivos de configuración de Sendmail que vienen por default en la instalación del Fail2Ban, en la carpeta action.d, reemplazamos el string de envío de correo de cada uno de los archivos sendmail por msmtp.

for FILE in /etc/fail2ban/action.d/sendmail*.conf; do cp "$FILE" "${FILE/sendmail/msmtp}"; done

sed -i 's/before = sendmail/before = msmtp/' /etc/fail2ban/action.d/msmtp*.conf

sed -i 's/after = sendmail/after = msmtp/' /etc/fail2ban/action.d/msmtp*.conf

sed -i 's/sbin\/sendmail -f <sender>/bin\/msmtp/g' /etc/fail2ban/action.d/msmtp*.conf

Completo los parámetros de configuración del msmtp, en el archivo /etc/fail2ban/action.d/msmtp-common.conf

	dest = pablo@midomino.com
	sender = fail2ban-noreply@midomino.com

Para configurar el envío de mails del Fail2Ban debemos modificar las siguientes líneas del archivo jail.local

.
destemail = pablo@midominio.com
sender = fail2ban-noreply@midominio.com
.
mta = msmtp
.
action = %(action_mwl)s
.

y modificamos el action del ssh para que notifique, en este caso es el archivo 7etc/fail2ban/jail.d/defaults-debian.conf

action = iptables[name=SSH, port=ssh, protocol=tcp] 
         msmtp-whois[name=SSH]

Resta reiniciar el servicio y deberíamos recibir las notificaciones por correo

service fail2ban restart

A modo de ejemplo lo haremos para una intranet.

Generar el CSR y la clave privada en el servidor web Apache.

Desde línea de comandos de la servidor web Apache generar un archivo csr-intranet.txt con el siguiente contenido:

[req]
default_bits = 2048
prompt = no
default_md = sha256
req_extensions = req_ext
distinguished_name = dn
[ dn ]
C=AR
ST=CABA
L=CABA
O=Mi Organizacion SA
OU=IT
emailAddress=admin@miorganizacion.com.ar
CN = intranet.miorganizacion.com.ar
[ req_ext ]
subjectAltName = @alt_names
[ alt_names ]
DNS.1 = intranet.miorganizacion.com.ar

Ahora ejecutar el siguiente comando:

openssl req -new -sha256 -nodes -out intranet.csr -newkey rsa:2048 -keyout intranet.key -config csr-intranet.txt

Copiar el archivo intranet.csr al servidor donde esta la CA (Windows Server)

Generar el certificado firmado por la CA Windows

Desde línea de comandos del servidor Windows (CA):

certreq -submit -attrib "CertificateTemplate:WebServer" intranet.csr

Se selecciona la CA -> Ok

Escribir el nombre del certificado «intranet.crt» -> Guardar

Configurar el sitio seguro en el servidor web Apache

Desde la linea de comandos activar el módulo SSL

a2enmod ssl

Editar el archivo /etc/apache2/sites-available/intranet.conf con la siguiente información:

<VirtualHost intranet.miorganizacion.com.ar:443>
   DocumentRoot "/usr/share/intranet"
   ServerName intranet.miorganizacion.com.ar
   ServerAlias intranet.miorganizacion.com.ar
   <IfModule mod_ssl.c>
       SSLEngine on
       SSLProtocol All -SSLv2 -SSLv3
       SSLHonorCipherOrder     on
       SSLCertificateFile /etc/apache2/intranet.crt
       SSLCertificateKeyFile /etc/apache2/intranet.key
   </IfModule>
</VirtualHost>

Restartear el servicio:

service apache2 restart

Acceder a https://intranet.miorganizacion.com.ar desde cualquier browser para verificar el certificado.

Para descargar la CA se puede acceder vía browser desde la URL:

https://CA.miorganizacion.com.ar/certsrv/

Seguimos experimentando el monitoreo de temperatura y humedad con el Arduino Uno y un sensor DHT22, ahora con unas modificaciones para que los datos los tome un Servidor o proxy Zabbix. Para ello fue necesario implementar un agente que escuche el puerto 10050 e interprete el protocolo que Zabbix entiende.

Según la documentación de la versión 3.4 se compone de un de 5 bytes, un de 8 bytes donde se establece el tamaño del comando o mensaje, y los datos en cuestión.

https://www.zabbix.com/documentation/3.4/manual/appendix/protocols/header_datalen

Las primeras líneas de nuestro código contienen el llamado a librerías conocidas por los proyectos anteriores, como son Ethernet.h y DHT.h, y la definición de las constantes, variables y parámetros que utilizaremos luego.

En la sección setup inicializamos sensor y servidor

y dentro del loop nos resta implementar las dos tareas que se ejecutan continuamente:
Por un lado leer el sensor (cada 3 segundos) y almacenar el valor en las variables humidity y temperature.
Y por otro lado, esperar un comando Zabbix, interpretarlo, y responder con el valor correspondiente.

La lectura del sensor ya la desarrollamos en anteriores, de la siguiente manera:

Para resolver la comunicación entre los agentes, esperamos un comando en formato Zabbix que ya sabemos estará formado por un byte 0x01, 8 bytes con el tamaño del comando, y finalmente el comando en sí.

Los comandos que vamos a identificar son los siguientes:

  • agent.ping
  • agent.hostname
  • agent.version
  • dht.temperature
  • dht.humidity

Las respuestas que implementaremos serán enviadas mediante la función zbx_print() que adaptará el mensaje al protocolo ya definido.

Podemos descargar la última versión del código completo publicado en Gitlab

Para verificar que los comandos funcionan correcamente podemos utilizar el comando zabbix_get

Los templates necesarios para que el Zabbix incorpore estos comandos se pueden descargar de los siguientes enlaces:

Monitoreo de últimos datos recibidos en Zabbix Server

Y agregamos un link para descarga del dashboard de Grafana que se observa en la siguiente imagen para mostrar los datos de mejor manera.

Dashboard de Grafana con origen de datos de Zabbix

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();
  }
}
Stop SOPA