lunes, 16 de enero de 2017

Script para migrar usuarios del sistema (Ubuntu 12.04) a servidor RADIUS (Freeradius/Daloradius)



En esta entrega voy a compartir un script que permite migrar los usuarios Linux (Ubuntu 12.04) a un servidor Freeradius.
Este escenario se aplica al caso que tengamos autentificando un servicio como por ejemplo el RAS con usuarios del sistema y necesitamos pasar la autentificación a RADIUS. Como se podrá observar este script tiene por objetivo que el proceso de migración sea trasparente para el usuario remoto que va a autentificarse.
Suponemos que tenemos instalado freeradius con soporte para mySQL, además de Daloradius para administración gráfica.
Previamente debemos copiar el archivo /etc/shadow desde el servidor RAS al servidor freeradius en la misma carpeta donde ejecutaremos el script y lo renombraremos como shadow.txt. Este archivo contiene los usuarios y password encriptados del sistema Linux; aprovecharemos el atributo radius Crypt-Password para insertar el password encriptado del usuario en la BBDD mysql de freeradius. Si no queremos importar determinado usuario sencillamente eliminamos la línea que lo contiene en el fichero shadow.txt, después de copiado.

EL script

#!/bin/sh
# Este script lee usuarios del fichero shadow y los inserta en la bd
# de freeradius/Daloradius.
# fichero q contendra las sentencias SQL
filebase="sql-insert-users.txt"
# Eliminanmos el fichero si existiera previamente
rm -f $filebase
# Creamos el fichero
touch $filebase
users=$(cat shadow.txt)
for u in $users
do
# Tomamos la primera columna q contienes el username
usuario=`echo $u|cut -d: -f1`
# Tomamos la segunda columna q contiene el password, el separador entre columnas es :
pass=`echo $u|cut -d: -f2`
echo "Creando usuario -> usuario=$usuario passw=$pass"
echo "INSERT INTO radcheck (username,attribute,op,value) VALUES ('$usuario','Crypt-Password','==','$pass');" >> $filebase
# la tabla userinfo es agregada por Daloradius al squema de freeradius, si no usamos Daloradius 
# comentar la sgte linea
echo "INSERT INTO userinfo (username) VALUES ('$usuario');" >> $filebase
done
# user y pass de mysql con derecheros a insertar en la tabla radcheck y/o userinfo en la BD radius
mysql --user=root --password=passroot radius < $filebase



miércoles, 13 de enero de 2016

El servicio VSFTPD en modo pasivo y el firewall IPTABLES en UBUNTU 12.04

En esta pequeña entrada comentare como configurar el servicio VSFTPD en modo pasivo y los ajustes correspondientes en las reglas IPTABLES para evitar problemas de acceso al servicio.


El servicio vsftpd nos permite instalar en un servidor FTP en Ubuntu y configurarlo en modo pasivo. En el siguiente link se explica la diferencia entre los modos activos y pasivos del servicio FTP (http://mimesadetrabajo.blogspot.com/2009/01/ftp-modo-pasivo-o-modo-activo.html).

A mi modo de ver esta concepción nos puede traer un dolor de cabeza, como fue en mi caso. El asunto es que en el modo pasivo el cliente pide un puerto al servidor para la conexión de datos, dicho puerto es aleatorio, de modo que: ¿Cómo configuramos iptables y vsftpd para conocer de antemano los puertos que serán abiertos? Y así evitar problemas de acceso al servicio.

VSFTPD

Editamos el fichero de configuración del servicio vsftpd en Ubuntu en la ruta /etc/vsftpd.conf y ajustamos los siguientes parámetros:

pasv_enable=YES
pasv_min_port=30000
pasv_max_port=65535

Con estos parámetros le estamos diciendo al servicio que trabaje en modo pasivo y que use el rango de puertos 30000 – 65535 para el tráfico de datos.

IPTABLES

Editamos nuestro script de iptables y configuramos las siguientes reglas

iptables -A INPUT -s $RED1 -p tcp  -- dport 20:21 -j ACCEPT
iptables -A OUTPUT  -s $RED1 -p tcp  --sport 20:21 -j ACCEPT
iptables -A INPUT -s $RED1 -p tcp  --dport 30000:65535 -j ACCEPT
iptables -A OUTPUT -s $RED1 -p tcp  --sport 30000:65535 -j ACCEPT

Estas reglas iptables permiten comunicación a la red definida en la variable RED1 a los rangos de puertos del server del 21-21 y del 30000-65535.

Como se aprecia en este pequeño articulo solo me limito a mostrar la configuración de ambos servicios para que trabajen coherentemente para el modo pasivo del vsftpd.

jueves, 3 de diciembre de 2015

C# - Registrar los Megabytes copiados a extraibles USB

Voy a exponer la lógica y los recursos de código empleados por mí en un programa C# para contabilizar y totalizar los megabyte que se copian en un PC hacia dispositivos extraíbles USB.


Los requerimientos del programa:
  •  Debe detectar todas las unidades de almacenamiento USB que se conectan al pc y contabilizar los megabytes copiados.
  • Debe reportar los megabyte copiados en un archivo de valores separados por coma (csv); un archivo por cada día con al menos la siguiente información: Nombre del volumen, Hora de conexión y Megabytes copiados.
  •  Debe ejecutarse como un proceso sin interface visible.
  • Debe garantizar iniciarse cuando se inicie Windows.

El objetivo de este artículo es publicar mi idea para retroalimentarme con otras ideas.

El núcleo de esta aplicación está sustentado sobre la clase DriverDetector.cs de codeproject.com (http://www.codeproject.com/Articles/18062/Detecting-USB-Drive-Removal-in-a-C-Program). 

Esta clase detecta el mensaje WM_DEVICECHANGE que Windows envía a todas las aplicaciones, este mensajes es enviado por Windows cuando ocurre un cambio en el hardware de la pc; adicionalmente se envía con el mensaje el parámetro WParam que contiene el código especifico del cambio ocurrido. Los valores que interesan de WParam son: 

DBT_DEVICEARRIVAL: es enviado cuando se conecta un dispositivo USB.
DBT_DEVICEQUERYREMOVE: se envía cuando se solicita la extracción del dispositivo.
DBT_DEVICEREMOVECOMPLETE: se envía cuando el dispositivo es removido completamente.

Consecuentemente con esto la clase DriverDetector.cs nos da la posibilidad de escribir código en los procedimientos de eventos:
OnDriveArrived: Código que se ejecutara cuando se conecte un dispositivo USB.
OnQueryRemove: Código a ejecutar antes de remover el dispositivo USB.
OnDriveRemove: Código para cuando se extraiga completamente el dispositivo.

Con estos elementos el trabajo consiste en codificar la siguiente lógica:
  


  • En el evento OnDriveArrived tomar el espacio libre del dispositivo y guardarlo en una variable A (mas adelante veremos que debe ser un arreglo de memoria bidimensional).
  • En el evento OnDriveRemove calcular A-A1 (el resultado es el tamaño de la informacion copiada al dispositivo), escribir el resultado en el archivo csv. (El calculo en este punto no es totalmente cierto; si se borra informacion el resultado del calculo no es exactamente la informacion copiada).

  •         En el evento OnQueryRemove volver a leer el espacio libre del dispositivo y guardarlo en otra variable A1 (debe ser en una columna del arreglo bidimensional).
      La clase DriverDetector.cs es perfecta, me quito el sombrero ante el autor, pero tiene una limitacion que por mas que intente no pude solventar.

     El asunto es el siguiente; para que window envie el submensaje (para llamarle de alguna manera) DBT_DEVICEQUERYREMOVE hay que “decirle explícitamente que lo haga” (esto incluye crear un archivo con ciertas características en la unidad entre otras cosas) de lo contrario no se envía. La clase DriverDetector.cs lo hace, pero solo para un dispositivo a la vez, de manera que no podemos controlar el evento OnQueryRemove para varios dispositivos conectados.

De modo que el punto 2 de la lógica no tenemos como resolverlo. La pregunta es:

¿Qué recurso de software tenemos para resolver esto?

Existe una clase en .NET (namespace System.IO) para monitorear cambios en un directorio y/o subdirectorios y provocar eventos para actuar ante estos cambios; la clase es FileSystemWatcher. Con esta clase se pueden inspeccionar cambios en un directorio tales como creación o eliminación de archivos.

Esta pudiera ser la solución, instanciando la clase para cada dispositivo USB detectados podemos tener el tamaño de los archivos creados (copiados) en la unidad USB. Pero esto tiene otro problema y es que cuando intentemos extraer el dispositivo Windows “nos dira” que el mismo está en uso. Lo lógico es que en el evento que ocurre antes del OnDriveRemove detengamos el monitoreo de la unidad USB por parte de la clase FileSystemWatcher, y ese evento es OnQueryRemove. De modo que estamos en las mismas.

SOLUCION

Mi solución fue desechar la escucha del mensaje DBT_DEVICEQUERYREMOVE y su correspondiente evento OnQueryRemove. En su lugar, cada 1 segundo voy a leer el espacio libre en el dispositivo y lo voy a restar a la lectura anterior, si la diferencia es positiva (mayor que cero) significa que el espacio libre va disminuyendo, por tanto hay en marcha un proceso de copia, entonces voy acumulando la diferencia en una variable (columna de un arreglo de memoria). Finalmente al extraer el dispositivo, el valor de esa variable va a ser el tamaño copiado al dispositivo USB.

Ilustrare esta solución con los “trozos” de código fundamentales.

Namespaces referenciados (independientes de los referenciados por la clase DriverDetector)

using System;
using System.Collections.Generic;
using Microsoft.Win32;
using System.Windows.Forms;
using System.Text;
using System.IO;
using System.Text;

Variables con alcance en todo el formulario

// Variables para almacenar el tamaño del disp., Bay libres y ocupados al conectar
long size0,Bay0,Ocup0;
// Variable de tipo DriveInfo (clase .NET q proporciona acceso a información sobre //una unidad
DriveInfo flash;
String NombreVolumen;
char LetraUnidad;
byte fila_matriz;

//Matrix string 26 filas x 6 columnas, 26 posibles unidades a conectar, 6 columnas
//0-Letra unidad,1-EtiquetaVolumen+Hora de conexión,2-Tamaño, 3-Byte libres al
//conectar,4-Byte Ocupados al conectar,5-Columna de apoyo al calculo,6-Byte copiados
public static string[,] usb_conectada = new string[26, 7];
string Hora_conexion;   

Procedimiento Main de program.cs




     El parámetro “install” si es pasado a la aplicación se crea una llave en el registro para garantizar la carga en cada inicio del sistema, lo contrario con el parámetro “uninstall”

      Evento Load del formulario


        Genarq es una clase del programa con métodos de apoyo a la solución del problema.

         La definición de la clase comienza así:



      Como se ve, incluye la declaración de una variable tIntervalo de tipo Timer (clase .NET para ejecutar métodos a intervalos regulares de tiempo, 1 segundo para nuestro caso).
     
       El método HeadFileCSVRW() y  AddLineFileCSV(string namefile,string linea)

       public static string HeadFileCSVRW()
        {
      string namefile = Application.StartupPath + Path.DirectorySeparatorChar.ToString() + "cusb_" + DateTime.Now.Day.ToString() + "_" + DateTime.Now.Month.ToString() + "_" + DateTime.Now.Year.ToString() + ".csv";
         if (!File.Exists(namefile))
          {
         using (FileStream fs = new FileStream(namefile, FileMode.CreateNew, FileAccess.Write,                FileShare.ReadWrite))
                {
                 using (TextWriter fila_csv = new StreamWriter(fs))
                 {
                 // LetraUnidad,Nombre+HoraConexion,Tamaño,MBL0,MBOcup,BLtmp,BCopiados
                fila_csv.WriteLine("Escrito por: Arquimides R. Ribeaux del Rio");                            fila_csv.WriteLine("Letra_Unidad,Etiqueta_Hora,GZ0,MBL_i,MB_o,MBL_Tmp,MBC");
                fila_csv.Close();
                    }
                }
            }
          return namefile;
         }


    public static void AddLineFileCSV(string namefile,string linea)
    {
  using (FileStream fs = new FileStream(namefile, FileMode.Append, FileAccess.Write, FileShare.ReadWrite))
        {
         using (TextWriter fila_csv = new StreamWriter(fs))
           {
            // LetraUnidad,Nombre+HoraConexion,Tamaño,MBL0,MBOcup,BLBorrado,BLCopiado
             fila_csv.WriteLine(linea);                        
             fila_csv.Close();
            }
         }
     }

     El primer método chequea la existencia del archivo csv, sino existe lo crea con un nombre que incluye la fecha del día y escribe las 2 primeras líneas del fichero y el segundo agrega cada linea al csv despues de extraido el dispositivo

      El procedimiento tProcess1Seg, código del evento Tick del Timer

   public static void tProcess1Seg(Object myobj, EventArgs myEA)
        {
            string lunidad;
            tIntervalo.Stop();      
            for (byte i = 0; i <= 25; i++)
            {
                if (frmMain.usb_conectada[i, 0] != null) //La letra de unidad NO es Null
                {
                  try
                  {
                    //     0 ,1, 2, 3, 4, 5 , 6  
                    // LetraUnidad,Nombre+HoraConexion,Tamaño,BL0,Ocupados,BLtmp,BCopiados
                    lunidad = frmMain.usb_conectada[i,0];
                    DriveInfo unidad = new DriveInfo(lunidad);
                    //BL al iniciar                       
                    long lec_anterior = (long)Convert.ToInt64(frmMain.usb_conectada[i,5]);  
                    //Lectura de espacio libre ahora
                    long lec_now = unidad.AvailableFreeSpace; 
                    // El espacio libre va disminuyendo por tanto estan copiando
                    if ((lec_anterior - lec_now) > 0 ) 
                    {
    long lec_tmp = (long)Convert.ToInt64(frmMain.usb_conectada[i,6])+(lec_anterior - lec_now);
    frmMain.usb_conectada[i,6] = lec_tmp.ToString();                           
                    }
                    frmMain.usb_conectada[i,5]=lec_now.ToString();                                             }
                    catch
                    {
                        tIntervalo.Start();
                        Application.DoEvents();
                        return;
                    }                                      
                }
            }                 
            tIntervalo.Enabled = true;
        }


      Procedimiento para el evento OnDriveArrived

  private void OnDriveArrived(object sender, DriveDetectorEventArgs e)
        {
         // LetraUnidad,Nombre+HoraConexion,Tamaño,BL0,Ocup0,BLBorrado,BLCopiado
         // e.Drive is the drive letter for the device which just arrived, e.g. "E:\\"                 try
            {
              flash = new DriveInfo(e.Drive);
              NombreVolumen = flash.VolumeLabel.ToString(); // obtener el nombre del volumen
              if (NombreVolumen.Length == 0)
               { NombreVolumen = "Sin_Nombre"; }
               // Obtener la primera letra de la cadena, la letra de unidad  
               LetraUnidad = e.Drive[0];   
               size0 = flash.TotalSize;
               Bay0 = flash.AvailableFreeSpace; // Espacio libre inicial
               Ocup0 = size0 - Bay0; // Espacio Ocupado inicial            
             // El ascii de letra unidad-65 para obtener la fila de la matriz           
              fila_matriz = Encoding.ASCII.GetBytes(LetraUnidad.ToString())[0];
             Hora_conexion=DateTime.Now.Hour.ToString()+":"+DateTime.Now.Minute.ToString()+":"DateTime.Now.Second.ToString();
              usb_conectada[fila_matriz - 65, 0] = e.Drive;
              usb_conectada[fila_matriz - 65, 1] = NombreVolumen + "_" + Hora_conexion;
              usb_conectada[fila_matriz - 65, 2] = size0.ToString();
              usb_conectada[fila_matriz - 65, 3] = Bay0.ToString();
              usb_conectada[fila_matriz - 65, 4] = Ocup0.ToString();
              usb_conectada[fila_matriz - 65, 5] = Bay0.ToString();
              usb_conectada[fila_matriz - 65, 6] = "0";
            }
            catch
            {
                Genarq.tIntervalo.Start();
                Application.DoEvents();
                Genarq.tIntervalo.Enabled = true;
                return;
            }

      Procedimiento para el evento OnDriveRemoved

  private void OnDriveRemoved(object sender, DriveDetectorEventArgs e)
        {        
            // Manipular unidad
            LetraUnidad = e.Drive[0];            
            fila_matriz = Encoding.ASCII.GetBytes(LetraUnidad.ToString())[0];
        
            //     0      ,           1       ,   2  , 3 ,  4    ,  5      ,    6   
            // LetraUnidad,Nombre+HoraConexion,Tamaño,BL0,Ocup,BLBorrado,BLCopiado

            long ar2, ar3, ar4, ar5, ar6;            
            ar2 = (long)Convert.ToInt64(usb_conectada[fila_matriz - 65, 2]); // Tamaño
            ar3 = (long)Convert.ToInt64(usb_conectada[fila_matriz - 65, 3]);  //BL al conectar
            ar4 = (long)Convert.ToInt64(usb_conectada[fila_matriz - 65, 4]); 
            ar5 = (long)Convert.ToInt64(usb_conectada[fila_matriz - 65, 5]);  //BL Tmp
            ar6 = (long)Convert.ToInt64(usb_conectada[fila_matriz - 65, 6]);  //B copiados

            string letra = usb_conectada[fila_matriz - 65, 0];
            string nombre = usb_conectada[fila_matriz - 65, 1];

            // Limpiar la fila de la matriz
            usb_conectada[fila_matriz - 65, 0] = null;
            usb_conectada[fila_matriz - 65, 1] = "Sin_Nombre";
            usb_conectada[fila_matriz - 65, 2] = string.Empty;
            usb_conectada[fila_matriz - 65, 3] = string.Empty;
            usb_conectada[fila_matriz - 65, 4] = string.Empty;
            usb_conectada[fila_matriz - 65, 5] = string.Empty;
            usb_conectada[fila_matriz - 65, 6] = string.Empty;
                        
            double Mb2, Mb3, Mb4, Mb5, Mb6;
            Mb2 = ((Convert.ToDouble(ar2) / 1024) / 1024) / 1024; //tamaño
            Mb3 = ((Convert.ToDouble(ar3) / 1024) / 1024); //MBL al conectar
            Mb4 = ((Convert.ToDouble(ar4) / 1024) / 1024); //MB Ocupados al conectar
            Mb5 = ((Convert.ToDouble(ar5) / 1024) / 1024); //BL Temporales
            Mb6 = ((Convert.ToDouble(ar6) / 1024) / 1024); //MB copiados

            string tam = Math.Round(Mb2, 2).ToString() + " GB";
            string mbl0 = Math.Round(Mb3, 2).ToString();
            string mbl1 = Math.Round(Mb4, 2).ToString();
            string mbl2 = Math.Round(Mb5, 2).ToString();
            string mbl3 = Math.Round(Mb6, 2).ToString();            

            string linea = letra + "," + nombre + "," + tam + "," + mbl0 + "," + mbl1 + "," + mbl2 + "," + mbl3;

    // Si pc queda encendida hasta dia sig. el proceso esta cargado de modo q no se           //ejecutar el Form_load
   // Debemos asegurarnos q escriba en el dia correcto

            string nombre_ruta_csv = Genarq.HeadFileCSVRW();

            Genarq.AddLineFileCSV(nombre_ruta_csv, linea);
        }

   
    En el siguiente enlace de Google Drive se puede descargar un fichero compactado (cusb.rar) que contiene el ejecutable cusb.exe.

        https://drive.google.com/file/d/0B1y9zYoMQe86S1djekRtV3RCZ28/view?usp=sharing

      Hasta aquí mi solución.  Al momento de terminar de redactar este artículo, me informan unos amigos (padre e hijo programadores) con los cuales compartí la idea inicial, que lograron controlar el DBT_DEVICEQUERYREMOVE e implementaron la solución utilizando directamente la función API de Windows que esta encapsulada en la clase .NET FileSystemWatcher; desarrollaran en Delphi/Pascal; para ellos el mejor lenguaje de programación que existe.