domingo, 30 de mayo de 2010

Comunicación SFTP (SSH)

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

1 comentario: