Hace poco tuve que modificar un componente que se comunicaba con FTP para bajar unos archivos de unas centrales Avaya, la modificación consistió en que la gente de Avaya cambió el protocolo de FTP a SFTP y resulta que no hay muchas opciones opensource para que este cambio sea transparente ya que el protocolo cambia significativamente (aunque los comandos no)
Para aclarar conceptos de FTP vs FTPS vs SFTP pueden consultar: http://www.compute-rs.com/es/consejos-781932.htm
Una breve definición en ingles: http://www.delphi3000.com/articles/article_4881.asp
* FTP — File Transfer Protocol. Popular and fast way of moving files between a client and a server. The problem with FTP is that it’s not secured by encryption, leaving files at risk of being compromised during transport.
* FTPS — File Transfer Protocol over SSL. FTPS is an encrypted flavor of the FTP protocol (kind of like how HTTPS is an encrypted flavor of HTTP).
* SFTP — SSH File Transfer Protocol. SFTP uses the Secure Shell (ie: SSH) protocol to encrypt all file transfer communications. SFTP is a bit more firewall friendly because it uses only 1 port and it’s also a bit more secure than FTPS. SFTP is gaining steam as the most preferred method of secure file transfer, particularly in infrastructures that favor unix but SFTP is quickly gaining steam in Windows environments as well.
Luego de mucho buscar en Internet me conseguí que existen muchos componente que permiten hacer los llamados SFTP, el problema es que casi todos eran pagos, solo conseguí uno OpenSource pero no me funcionó (SharpSSH) por lo que tuve que implementar otro tipo de solución.
La solución que me funcionó perfectamente no utiliza un componente para los llamados SSH, sino que utiliza las capacidades de scripting de una aplicación OpenSource usada como cliente SFTP llamada WinSCP.
A continuación el core de las llamadas a la aplicación WinSCP para poder hacer comandos SFTP
public static string EjecutarComandoSFTP(string logname, string Comando, string IP, string Usuario, string Password, string Host, string Port, string HostKey)
{
/// Run hidden WinSCP process
Process winscp = new Process();
//ruta del programa WinSCP.com
winscp.StartInfo.FileName = ConfigurationSettings.AppSettings["rutaWinSCP"];
//se guarda el archivo de log con el resultado de la operación
logname = logname.Replace(".xml"," " + Comando.Substring(0,Comando.IndexOf(" ") + 1) + ".xml");
winscp.StartInfo.Arguments = "/log=\"" + logname + "\"";
winscp.StartInfo.UseShellExecute = false;
winscp.StartInfo.RedirectStandardInput = true;
winscp.StartInfo.RedirectStandardOutput = true;
winscp.StartInfo.CreateNoWindow = true;
winscp.Start();
/// Feed in the scripting commands
winscp.StandardInput.WriteLine("option batch abort");
winscp.StandardInput.WriteLine("option confirm off");
//Comando de conexión, pendiente con el hsotkey o finger print
string ComandoFTP = "open sftp://" + Usuario + ":" + Password + "@" + Host + ":" + Port + " -hostkey=\"" + HostKey + "\"";
winscp.StandardInput.WriteLine(ComandoFTP);
winscp.StandardInput.WriteLine(Comando);
winscp.StandardInput.Close();
/// resultado de la ejecución del comando
string output = winscp.StandardOutput.ReadToEnd();
Log("Resultado de EjecutarComandoSFTP : " + output);
/// Wait until WinSCP finishes
winscp.WaitForExit();
return logname;
}
Básicamente lo que hace el código en cada llamada a EjecutarComandoSFTP es ejecutar el programa WinSCP.com pasando los parametros de conexión al servidor SFTP y pasando el comando a ejecutar, el resultado de la ejecución es guardado en un archivo XML creado por WinSCP.com el cual detalla los resultados del comando enviado.
Ejemplo de llamada para ejecutar un LS (un listado del directorio activo)
logname = EjecutarComandoSFTP(lognameBase,"LS " + DirectorioRemoto ,IP,Usuario,Password,IP,Puerto,HostKey);
Es muy importante manejar el nombre del Log para cada comando ya que necesitan de ese archivo para poder leer los resultados de la ejecución.
Un tip que hay que tener en consideración para los parámetros de conexión es que hay que pasar el Host Key FingerPrint del servidor al cual nos estamos conectando, si no lo tenemos podemos utilizar a WinScp.exe para obtenerlo o luego del primer intento de conexión, obtener el key del Log donde se registra la falla. un ejemplo de este key es: ssh-rsa 1024 9b:70:11:10:18:3a:68:ed:66:e4:fd:b6:6c:b3:97:0e
Luego de ejecutar el comando, el resto es simplemente procesar el XML para leer la respuesta, en mi caso yo necesitaba buscar un archivo con un patrón en particular dentro de la ejecución del LS para que si existe dicho archivo, proceder a bajarlo
XPathNodeIterator myXPathNodeIterator2 = (CrearNavegador(sTextoXml2)).Select ("//files/file/filename[starts-with(@value, '"+sNombreArchivoCentralRemoto+"')]");
Utilizando XPATH es muy facil sacar información bien precisa del XML, en el ejemplo anterior obtengo todos los nombres de archivos que comiencen con el patrón dado
Luego hago un ciclo sobre los archivos que coinciden con el patrón y obtengo el nombre real del archivo para ser bajado
while (myXPathNodeIterator2.MoveNext())
{ sNombreArchivoCentralRemoto = BuscaCampoXML(myXPathNodeIterator2,".//@value");
Y ejecuto nuevamente la app WinSCP.com para bajar el archivo, aprovechando para anexar su contenido a uno ya existente en el directorio donde los bajo y ademas eliminando el archivo remoto, todo en un solo comando
logname = EjecutarComandoSFTP(lognameBase,"get " + DirectorioRemoto+ sNombreArchivoCentralRemoto + " " + NombreArchivoLocal + " -append -delete" ,IP,Usuario,Password,IP,Puerto,HostKey);
Luego de eso, vuelvo a leer el archivo XML para ver el resultado de la operación:
string sTextoXml3 = LeerArchivo(System.AppDomain.CurrentDomain.BaseDirectory + logname,"");
if(sTextoXml3 != null && sTextoXml3.Length > 0){
XPathNodeIterator myXPathNodeIterator3 = (CrearNavegador(sTextoXml3)).Select ("//download/result");
string sResultado = BuscaCampoXML(myXPathNodeIterator3,".//@success");
if(sResultado == "true")
Log("Archivo bajado, agregado y eliminado exitosamente");
Como probar este tema? Existe un servidor SSH muy practico y opensource que es ideal para probar, se llama FreeSSH
Si desea implementar una solución de este tipo, por favor contácteme para que coordinemos una reunión para revisar sus requerimientos y como puedo ayudarlo
domingo, 30 de mayo de 2010
martes, 18 de mayo de 2010
Validador de Archivos
Ver en CodePlex http://filevalidator.codeplex.com/
En un proyecto del sector bancario existía un requerimiento en donde los clientes suben archivos al servidor para procesar una serie de pagos, básicamente la aplicación WEB guarda el archivo en una ruta en especifico y luego un servicio de Windows agarra el archivo y lo carga en BD para correr una serie de validaciones y procesos.
El requerimiento indicaba que había que validar el formato del archivo de texto antes de proceder a cargarlo en BD, por lo cual surge el presente componente.
El Componente permite realizar la validación de archivos (txt, imagenes, PDF, etc) actualmente solo tiene implementado la parte de los txt, permite validar un archivo con cabecera o no, con registros o no, permite la validación de cada columna del archivo.
Ejemplos:
Validar un archivo que tenga 6 columnas de header y al menos 2 registros (1 header y 1 detalle) El detalle debe tener 5 columnas
//Nota: El separador de columnas es punto y coma (;) pero es seteable para cualquier caracter en el header y en el detalle
ValidateContentFile target = new ValidateContentFile();
target.CantidadMinimaLineas = 2;
target.ValidarContenido = new BodyValidation();
target.ValidarContenido.CantidadMinColumnas = 5;
target.ValidarHeader = new HeaderValidation();
target.ValidarHeader.CantidadColumnas =6;
//Puede indicar la ruta o pasar un array de Bytes
DTOresultado expected = target.ValidateContent(FilePath, ValidateContentFile.ValidationType.OnlyText);
Validar un archivo que tenga 6 columnas de header y al menos 2 registros (1 header y 1 detalle) El detalle debe tener 5 columnas
Y ademas que la primera columna del Header sea un Rif, y la segunda sea una número de cuenta (20 digitos)
Ademas se desea que en el contenido tenga en la cuarta columna números que inicien en 0174 o 0428
ValidateContentFile target = new ValidateContentFile();
target.CantidadMinimaLineas = 2;
target.ValidarContenido = new BodyValidation();
target.ValidarContenido.CantidadMinColumnas = 5;
target.ValidarHeader = new HeaderValidation();
target.ValidarHeader.CantidadColumnas =6;
//se indica la expresión regular a usar en este caso para el header, se debe hacer en el orden que se esperan las columnas, si no se desea aplicar regex para una columna se debe dejar en blanco ""
target.ValidarHeader.sRegexColumnas = new string[] { "^[jJ]\\d{9}\\b\\b", "\\b\\d{20}\\b", " ","","",""};
//Se especifica las expresiones regulares para el detalle del archivo
target.ValidarContenido.sRegexColumnas = new string[] { "", "", "", "^(?!0174)(?!0428)\\d+", "" };
//Puede indicar la ruta o pasar un array de Bytes
DTOresultado expected = target.ValidateContent(FilePath, ValidateContentFile.ValidationType.OnlyText);
Pueden bajarlo en http://filevalidator.codeplex.com/
Por construir:
1- Documentación
2- Enriquecer la cantidad de información de respuesta del componente
3- Permitir que el componente valide todo el archivo antes de parar la revisión
4- Incluir las validaciones de imagen, PDF y otras
lunes, 10 de mayo de 2010
Validaciones: RIF en Venezuela
Para Validar RIF , código c#
public bool validarRif(string sRif)
{
bool bResultado = false;
int iFactor = 0;
sRif = sRif.Replace("-", "");
if (sRif.Length < 10)
sRif = sRif.ToUpper().Substring(0, 1) + sRif.Substring(1, sRif.Length - 1).PadLeft(9, '0');
string sPrimerCaracter = sRif.Substring(0, 1).ToUpper();
switch (sPrimerCaracter)
{
case "V": iFactor = 1; break;
case "E": iFactor = 2; break;
case "J": iFactor = 3; break;
case "P": iFactor = 4; break;
case "G": iFactor = 5; break;
}
if (iFactor > 0)
{
int suma = ((int.Parse(sRif.Substring(8, 1))) * 2)
+ ((int.Parse(sRif.Substring(7, 1))) * 3)
+ ((int.Parse(sRif.Substring(6, 1))) * 4)
+ ((int.Parse(sRif.Substring(5, 1))) * 5)
+ ((int.Parse(sRif.Substring(4, 1))) * 6)
+ ((int.Parse(sRif.Substring(3, 1))) * 7)
+ ((int.Parse(sRif.Substring(2, 1))) * 2)
+ ((int.Parse(sRif.Substring(1, 1))) * 3)
+ (iFactor * 4);
float dividendo = suma / 11;
int DividendoEntero = (int)dividendo;
int resto = 11 - (suma - DividendoEntero * 11);
if (resto >= 10 || resto < 1)
resto = 0;
if (sRif.Substring(9, 1).Equals(resto.ToString()))
{
bResultado = true;
}
}
return bResultado;
}
public bool validarRif(string sRif)
{
bool bResultado = false;
int iFactor = 0;
sRif = sRif.Replace("-", "");
if (sRif.Length < 10)
sRif = sRif.ToUpper().Substring(0, 1) + sRif.Substring(1, sRif.Length - 1).PadLeft(9, '0');
string sPrimerCaracter = sRif.Substring(0, 1).ToUpper();
switch (sPrimerCaracter)
{
case "V": iFactor = 1; break;
case "E": iFactor = 2; break;
case "J": iFactor = 3; break;
case "P": iFactor = 4; break;
case "G": iFactor = 5; break;
}
if (iFactor > 0)
{
int suma = ((int.Parse(sRif.Substring(8, 1))) * 2)
+ ((int.Parse(sRif.Substring(7, 1))) * 3)
+ ((int.Parse(sRif.Substring(6, 1))) * 4)
+ ((int.Parse(sRif.Substring(5, 1))) * 5)
+ ((int.Parse(sRif.Substring(4, 1))) * 6)
+ ((int.Parse(sRif.Substring(3, 1))) * 7)
+ ((int.Parse(sRif.Substring(2, 1))) * 2)
+ ((int.Parse(sRif.Substring(1, 1))) * 3)
+ (iFactor * 4);
float dividendo = suma / 11;
int DividendoEntero = (int)dividendo;
int resto = 11 - (suma - DividendoEntero * 11);
if (resto >= 10 || resto < 1)
resto = 0;
if (sRif.Substring(9, 1).Equals(resto.ToString()))
{
bResultado = true;
}
}
return bResultado;
}
Validaciones: Módulo 11 (permite validar números de cuenta bancarias)
Los números de cuenta bancaria en Venezuela llevan un par de dígitos de control para evitar posibles problemas de transcripción. La forma de validarlos es muy sencilla.
En primer lugar, debemos darnos cuenta de que un número de cuenta completo está formado por varias partes:
En total, 20 dígitos.
.
El primero de los dígitos de control se calcula a partir de los cuatro dígitos de la entidad más los cuatro dígitos de la sucursal.
El segundo dígito de control se calcula a partir de los diez dígitos de la cuenta.
El algoritmo es de tipo checksum, en el que se multiplica cada dígito de la cuenta empezando por el de más a la derecha por un peso específico que se obtiene de la siguiente tabla según su posición. Estas multiplicaciones se suman y si el resultado es s, se el dígito de control se calcula de la siguiente manera:
En primer lugar, debemos darnos cuenta de que un número de cuenta completo está formado por varias partes:
- 4 dígitos que identifican a la entidad bancaria
- 4 dígitos que identifican a la sucursal
- 2 dígitos de control
- 10 dígitos que son el número de cuenta dentro de la sucursal
En total, 20 dígitos.
.
El primero de los dígitos de control se calcula a partir de los cuatro dígitos de la entidad más los cuatro dígitos de la sucursal.
El segundo dígito de control se calcula a partir de los diez dígitos de la cuenta.
El algoritmo es de tipo checksum, en el que se multiplica cada dígito de la cuenta empezando por el de más a la derecha por un peso específico que se obtiene de la siguiente tabla según su posición. Estas multiplicaciones se suman y si el resultado es s, se el dígito de control se calcula de la siguiente manera:
- dc=(11-(s mod 11))
- si (dc==11) entonces dc=0
- si dc (dc==10) entonces dc=1
A continuación muestro como implementar la validación módulo 11 en .NET y SQL Server
public bool ValidaModulo11(string cuentaCliente)
{
string entidad = cuentaCliente.Substring(0, 4);
string sucursal = cuentaCliente.Substring(4, 4);
string DC = cuentaCliente.Substring(8, 2);
string cuenta = cuentaCliente.Substring(10);
string dcCalculado = DigitosControl(entidad, sucursal, cuenta).ToString();
if (dcCalculado != DC)
{
return false;
}
else
{
return true;
}
}
static string DigitosControl(string entidad, string sucursal, string cuenta)
{
string d = dc(entidad + sucursal, false);
d += dc(sucursal + cuenta, true);
return d;
}
static string dc(string numero, bool esCuenta)
{
int[] pesos;
if (esCuenta == false)
{
pesos = new int[] { 3, 2, 7, 6, 5, 4, 3, 2, 5, 4, 3, 2 };
}
else
{
pesos = new int[] { 3, 2, 7, 6, 5, 4, 3, 2, 7, 6, 5, 4, 3, 2 };
}
int contador = 0;
long s = 0, d;
bool fin = false;
while (fin == false)
{
d = int.Parse(new string(numero[contador], 1));
s += d * pesos[contador];
contador++;
if (contador == numero.Length)
fin = true;
}
int resultado = (int)(11 - (s % 11));
if (resultado == 10)
resultado = 0;
else if (resultado == 11)
resultado = 1;
return resultado.ToString();
}
}
EN SQL:
CREATE FUNCTION [dbo].[validaModulo11]
(
@cuentaBancaria nvarchar(30)
)
RETURNS bit -- 0 cuenta incorrecta / 1 cuenta correcta
AS
BEGIN
--
--Variable con el valor que retornara la función.
DECLARE @Resultado bit
--
DECLARE @subCadena1 nvarchar(10)
DECLARE @subCadena2 nvarchar(15)
--
DECLARE @control nvarchar(5)
DECLARE @peso1 nvarchar(10)
DECLARE @peso2 nvarchar(15)
SET @Resultado= 0
if (len(@cuentaBancaria)= 20 and ISNUMERIC(@cuentaBancaria)=1) --Es un numero de cuenta valido
begin
SET @peso1 ='32765432'
SET @peso2='32765432765432'
SET @subCadena1=SUBSTRING(@cuentaBancaria,1,8)
SET @subCadena2=SUBSTRING(@cuentaBancaria,5,4)+SUBSTRING(@cuentaBancaria,11,10)
select @control=dbo.calcularControlBanco(@peso1,@subCadena1)+ dbo.calcularControlBanco(@peso2,@subCadena2)
IF(@control=SUBSTRING(@cuentaBancaria,9,2))
SET @Resultado= 1
end
RETURN @Resultado
END
GO
CREATE FUNCTION [dbo].[calcularControlBanco]
(
@peso nvarchar(20),
@digitos nvarchar(20)
)
RETURNS nvarchar(5)
AS
BEGIN
DECLARE @suma int
DECLARE @control int
DECLARE @varIndice int
set @suma=0
set @varIndice=1
WHILE len(@digitos)>=@varIndice
BEGIN
SET @suma = @suma + cast(CAST(SUBSTRING(@digitos,@varIndice,1) AS INT)* CAST(SUBSTRING(@peso,@varIndice,1) AS INT) as nvarchar(30))
SET @varIndice=@varIndice+1
END
set @control= 11-(@suma%11)
if(@control=10)
set @control=0
else
begin
if (@control =11)
set @control=1
end
return convert(nvarchar(5),@control)
END
martes, 4 de mayo de 2010
Revisar archivos con antivirus
En mi publicación anterior comenté como añadir un nivel extra de seguridad al guardar archivos de los usuarios en nuestras aplicaciones, en dicho articulo hablé de las validaciones de contenido, en esta publicación voy a hablar de un nivel mas avanzado de seguridad para que los archivos de los usuarios no signifiquen riesgos para nuestras aplicaciones ni para otros usuarios.
El EICAR test file consiste en 68 caracteres ASCII (70 si lo generamos con un editor de texto, debido a los códigos de retorno y final de línea que suelen insertar los editores).
Deberías recibir una advertencia de virus EICAR cuando descargue algunos de esos archivos (al menos del primero), o al intentar ejecutarlo, o descomprimirlo.
EICAR-STANDARD-ANTIVIRUS-TEST-FILE!
En el cliente donde se implementó el mecanismo de seguridad se plantearon las siguientes alternativas:
1- Guardar el archivo, esperar y ver si existe o no (asumiendo que el antivirus del servidor funciona), esto causa que dependamos que el antivirus del servidor efectivamente revise el archivo que se le hizo upload. Adicionalmente genera un tiempo de espera adicional fijo y existe la posibilidad que la espera sea menor al tiempo necesario por el antivirus.
2- Hacer llamado a un antivirus a través de un componente diseñado para tal fin como http://www.opswat.com/metascan.shtml el cual es pago, pero es una opción que da mucha seguridad
3- Hacer uso de antivirus free que provee API para ser llamado como http://www.wolfereiter.com/antivirus.aspx , pero se tiene el riesgo de la calidad de la detección de virus ya que no es una herramienta muy conocida
4- Utilizar Apis de antivirus como el que ustedes utilizan Symantec Scan Engine, es la opción mas segura ya que se utiliza una herramienta conocida en el mercado por su seguridad, lo malo es que hay que pagar licencia para usar ese api.
La recomendación al cliente fue:
Si se puede invertir dinero a nivel de licencias, recomendamos la opción 2 (licencia core o lite), de segundo recomendamos la opción 4. Si no se desea invertir dinero a nivel de licencias, por el desconocimiento de la confiabilidad del motor de antivirus de la opción 3, recomendamos la opción 1.
El cliente seleccionó la opción 4 ya que poseía ciertas licencias con ese proveedor. En este articulo voy a hablar de la opción de utilizar Symantec Scan Engine
La solución se basa en el uso del protocolo ICAP para mandar requests a un motor de antivirus para que se revise un archivo dado. Básicamente consiste en mandar mensajes con un formato en especifico vía Sockets a un servidor ICAP, en antivirus hace su trabajo y luego nos responde con un mensaje indicando el resultado de la revisión.
En este link pueden ver en detalle la especificación de ICAP para saber como armar los mensajes y como interpretar las respuestas. En este link pueden ver la especificación y ejemplos para Symantec
De este blog saqué el código sobre el cual se basó la solución ofrecida al cliente
Como probarlo sin ponernos en Riesgo?
EICAR, European Institute for Computer Antivirus Research (en español Instituto Europeo para la Investigación de los Antivirus Informáticos), desarrolló lo que hoy es conocido como EICAR test file, o archivo de prueba EICAR.
El EICAR test file sirve justamente para probar la funcionalidad del software antivirus, dándole a éste la oportunidad de detectarlo durante los procesos de escaneo, al mismo tiempo que no implica un riesgo para la seguridad de la computadora en la cuál se efectúa la prueba, sencillamente porque no se trata realmente de un virus.
Los mayores fabricantes de antivirus actuales, lo soportan. De todos modos es importante hacer notar, que no porque un antivirus detecte el EICAR test file, significa que ese antivirus es capaz de reconocer y bloquear todo tipo de código maligno (comúnmente llamado "malware", ya que involucra a todo tipo de software capaz de causar algún daño, y no solo a los virus).
La recomendación es usar el archivo EICAR, simplemente descargalo o genera un nuevo archivo EICAR. El software antivirus (API) debería detectar su presencia como la de un "virus" llamado "EICAR-AV-Test", "Eicar Archivo de prueba" (recordemos que NO es un virus). Ver el siguiente link para mayor información:
Los caracteres son:
X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*
Todas las letras están en mayúsculas, y sin espacios. El tercer carácter (X5O...) es la letra "O" no el número cero).
Para crear un archivo de prueba, copia y pega la línea anterior al bloc de notas, y graba su contenido con el nombre que quiera y la extensión .COM
También puede descargarlo de las siguientes direcciones. El formato de la primera (.COM), puede servirle para examinar si su antivirus intercepta la descarga y posterior ejecución, la segunda simplemente visualiza el código en el navegador (como un archivo de texto, .TXT), y las demás implementan la acción con archivos comprimidos (.ZIP):
Cuando el EICAR.COM se ejecuta, no hay peligro de daño alguno, debido a que NO se trata de un virus real), todo lo que ocurrirá será la aparición de una ventana DOS con este único texto:
Si desea implementar un mecanismo de seguridad para los archivos que suben los usuarios a sus aplicaciones, por favor ponerse en contacto para realizar una evaluación de sus requerimientos
Suscribirse a:
Entradas (Atom)