• Sign in
 
  •  
  • MSDN Blogs
  • Microsoft Blog Images
  • More ...
Common Tasks
  • Blog Home
  • About
  • RSS for posts
  • Atom
Blog - News
Search
Twitter (@dmossberg)
Tags
  • ASP
  • ASP.NET
  • Common Language Runtime (CLR)
  • Debugging
  • Ejemplos de Código
  • Failed Request Tracing
  • Herramientas
  • IIS 6.0
  • IIS 7.0
  • Kerberos
  • Log Parser
  • Pages
  • Seguridad
  • Silverlight
  • SSL/TLS
Recent Posts
  • Digest Authentication in System.Net classes don't fully comply with RFC2617

    Posted 3 months ago
    by Daniel Mossberg
  • Corrupción de las claves RSA y la importancia de hacer backup

    Posted over 1 year ago
    by Daniel Mossberg
  • Modelos de programación en ASP.NET: Web Forms, MVC y Web Pages

    Posted over 1 year ago
    by Daniel Mossberg
  • HttpException: An error occurred while attempting to impersonate

    Posted over 3 years ago
    by Daniel Mossberg
  • Problemas al subir ficheros a una aplicación ASP.NET

    Posted over 3 years ago
    by Daniel Mossberg
Archives
Archives
  • February 2013 (1)
  • May 2012 (2)
  • November 2010 (1)
  • August 2010 (1)
  • April 2010 (1)
  • March 2010 (2)
  • February 2010 (2)
  • January 2010 (2)
  • December 2009 (5)
  • October 2009 (1)
  • September 2009 (3)
  • August 2009 (1)
  • July 2009 (2)
  • May 2009 (2)
  • April 2009 (4)
  • February 2009 (1)
  • January 2009 (1)
  • December 2008 (1)
Web Developer Support Blogs
  • If broken it is, fix it you should

  • Notes from a dark corner

  • Never doubt thy debugger

  • Speaking of which...

  • Desarrollo Web

Server and Tools Blogs
  • Windows 8 app developer blog

  • The Visual Studio Blog

  • .NET Web Development and Tools Blog

  • .NET Framework Blog

  • The Windows Phone Developer Blog

  • ScottGu's Blog

  • Scott Hanselman's Computer Zen

  • Subscribe via RSS
Sort by: Most Recent | Most Views | Most Comments
Excerpt View | Full Post View
  • The code is out there

    Digest Authentication in System.Net classes don't fully comply with RFC2617

    Posted 3 months ago
    by Daniel Mossberg
    • 0 Comments
    Symptoms
    Using a class from the System.Net namespace, e.g. HttpWebRequest, to authenticate to a non-Microsoft web server using Digest authentication might result in some error condition, e.g. an HTTP 500 error:
     
     

    HTTP/1.1 500 Internal Server Error
    Date: Fri, 30 Nov 2012 12:15:15 GMT
    Server: Apache/2.2.22 (Win32)
    Content-Length: 547
    Connection: close
    Content-Type: text/html; charset=iso-8859-1

    <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
    <html><head>
    <title>500 Internal Server Error</title>
    </head><body>
    < h1>Internal Server Error</h1>
    <p>The server encountered an internal error or
    misconfiguration and was unable to complete
    your request.</p>
    </body></HTML>

     
    Cause
    System.Net classes don't include the query string in the 'Uri' attribute of the digest authentication header. This is a violation of the RFC, and some web server implementations reject those requests.
     
    Resolution
    The below workaround provides an implementation of the Digest authentication protocol which generates an Authentication header compliant with the RFC2617. The below code sample is based on the sample provided originally in the following forum: http://stackoverflow.com/questions/3109507/httpwebrequests-sends-parameterless-uri-in-authorization-header.

    IMPORTANT: This sample code is provided as-is and is intended for sample purposes only. It is provided without warranties and confers no rights.
             

    using System;

    using System.Globalization;

    using System.IO;

    using System.Net;

    using System.Security.Cryptography;

    using System.Text;

    using System.Text.RegularExpressions;

     

    namespace DigestClient

    {

        publicclassDigestHttpWebRequest

        {

            privatestring _user;

            privatestring _password;

            privatestring _realm;

            privatestring _nonce;

            privatestring _qop;

            privatestring _cnonce;

            privateAlgorithm _md5;

            privateDateTime _cnonceDate;

            privateint _nc;

     

            privatestring _requestMethod = WebRequestMethods.Http.Get;

            privatestring _contentType;

            privatebyte[] _postData;

     

            public DigestHttpWebRequest(string user, string password)

            {

                _user = user;

                _password = password;

            }

     

            publicstring Method

            {

                get { return _requestMethod; }

                set { _requestMethod = value; }

            }

     

            publicstring ContentType

            {

                get { return _contentType; }

                set { _contentType = value; }

            }       

     

            publicbyte[] PostData

            {

                get { return _postData; }

                set { _postData = value; }

            }

     

            publicHttpWebResponse GetResponse(Uri uri)

            {

                HttpWebResponse response = null;

                int infiniteLoopCounter = 0;

                int maxNumberAttempts = 2;

     

                while ((response == null ||

                    response.StatusCode != HttpStatusCode.Accepted) &&

                    infiniteLoopCounter < maxNumberAttempts)

                {

                    try

                    {

                        var request = CreateHttpWebRequestObject(uri);

     

                        // If we've got a recent Auth header, re-use it!

                        if (!string.IsNullOrEmpty(_cnonce) &&

                            DateTime.Now.Subtract(_cnonceDate).TotalHours< 1.0)

                        {

                            request.Headers.Add("Authorization", ComputeDigestHeader(uri));

                        }

     

                        try

                        {                       

                            response = (HttpWebResponse)request.GetResponse();

                        }

                        catch (WebException webException)

                        {

                            // Try to fix a 401 exception by adding a Authorization header

                            if (webException.Response != null &&

                                ((HttpWebResponse)webException.Response).StatusCode == HttpStatusCode.Unauthorized)

                            {

     

                                var wwwAuthenticateHeader = webException.Response.Headers["WWW-Authenticate"];

                                _realm = GetDigestHeaderAttribute("realm", wwwAuthenticateHeader);

                                _nonce = GetDigestHeaderAttribute("nonce", wwwAuthenticateHeader);

                                _qop = GetDigestHeaderAttribute("qop", wwwAuthenticateHeader);

                                _md5 = GetMD5Algorithm(wwwAuthenticateHeader);

     

                                _nc = 0;

                                _cnonce = newRandom().Next(123400, 9999999).ToString();

                                _cnonceDate = DateTime.Now;

     

                                request = CreateHttpWebRequestObject(uri, true);

     

                                infiniteLoopCounter++;

                                response = (HttpWebResponse)request.GetResponse();

                            }

                            else

                            {

                                throw webException;

                            }

                        }

     

                        switch (response.StatusCode)

                        {

                            caseHttpStatusCode.OK:

                            caseHttpStatusCode.Accepted:

                                return response;

                            caseHttpStatusCode.Redirect:

                            caseHttpStatusCode.Moved:

                                uri = newUri(response.Headers["Location"]);

     

                                // We decrement the loop counter, as there might be a variable number of redirections which we should follow

                                infiniteLoopCounter--;

                                break;                           

                        }

     

                    }

                    catch (WebException ex)

                    {

                        throw ex;

                    }

                }

     

                thrownewException("Error: Either authentication failed, authorization failed or the resource doesn't exist");

            }

     

            privateHttpWebRequest CreateHttpWebRequestObject(Uri uri, bool addAuthenticationHeader)

            {

                var request = (HttpWebRequest)WebRequest.Create(uri);

                request.AllowAutoRedirect = false;

                request.Method = this.Method;

     

                if (!String.IsNullOrEmpty(this.ContentType))

                {

                    request.ContentType = this.ContentType;

                }

     

                if (addAuthenticationHeader)

                {

                    request.Headers.Add("Authorization", ComputeDigestHeader(uri));

                }

               

                if (this.PostData != null && this.PostData.Length > 0)

                {

                    request.ContentLength = this.PostData.Length;

                    Stream postDataStream = request.GetRequestStream(); //open connection

                    postDataStream.Write(this.PostData, 0, this.PostData.Length); // Send the data.

                    postDataStream.Close();

                }

                elseif (

                    this.Method == WebRequestMethods.Http.Post &&

                    (this.PostData == null || this.PostData.Length == 0))

                {

                    request.ContentLength = 0;

                }

               

                return request;

            }

     

            privateHttpWebRequest CreateHttpWebRequestObject(Uri uri)

            {

                return CreateHttpWebRequestObject(uri, false);

            }

     

            privatestring ComputeDigestHeader(Uri uri)

            {

                _nc = _nc + 1;

     

                string ha1, ha2;

     

                switch (_md5)

                {

                    caseAlgorithm.MD5sess:

     

                        var secret = ComputeMd5Hash(string.Format(CultureInfo.InvariantCulture, "{0}:{1}:{2}", _user, _realm, _password));

                        ha1 = ComputeMd5Hash(string.Format(CultureInfo.InvariantCulture, "{0}:{1}:{2}", secret, _nonce, _cnonce));

                        ha2 = ComputeMd5Hash(string.Format(CultureInfo.InvariantCulture, "{0}:{1}", this.Method, uri.PathAndQuery));

     

                        var data = string.Format(CultureInfo.InvariantCulture, "{0}:{1:00000000}:{2}:{3}:{4}",

                            _nonce,

                            _nc,

                            _cnonce,

                            _qop,

                            ha2);

     

                        var kd = ComputeMd5Hash(string.Format(CultureInfo.InvariantCulture, "{0}:{1}", ha1, data));

     

                        returnstring.Format("Digest username=\"{0}\", realm=\"{1}\", nonce=\"{2}\", uri=\"{3}\", " +

                            "algorithm=MD5-sess, response=\"{4}\", qop={5}, nc={6:00000000}, cnonce=\"{7}\"",

                            _user, _realm, _nonce, uri.PathAndQuery, kd, _qop, _nc, _cnonce);

     

                    caseAlgorithm.MD5:

     

                        ha1 = ComputeMd5Hash(string.Format("{0}:{1}:{2}", _user, _realm, _password));

                        ha2 = ComputeMd5Hash(string.Format("{0}:{1}", this.Method, uri.PathAndQuery));

                        var digestResponse =

                            ComputeMd5Hash(string.Format("{0}:{1}:{2:00000000}:{3}:{4}:{5}", ha1, _nonce, _nc, _cnonce, _qop, ha2));

     

                        returnstring.Format("Digest username=\"{0}\", realm=\"{1}\", nonce=\"{2}\", uri=\"{3}\", " +

                            "algorithm=MD5, response=\"{4}\", qop={5}, nc={6:00000000}, cnonce=\"{7}\"",

                            _user, _realm, _nonce, uri.PathAndQuery, digestResponse, _qop, _nc, _cnonce);

     

                }

     

                thrownewException("The digest header could not be generated");

            }

     

            privatestring GetDigestHeaderAttribute(string attributeName, string digestAuthHeader)

            {

                var regHeader = newRegex(string.Format(@"{0}=""([^""]*)""", attributeName));

                var matchHeader = regHeader.Match(digestAuthHeader);

                if (matchHeader.Success)

                    return matchHeader.Groups[1].Value;

                thrownewApplicationException(string.Format("Header {0} not found", attributeName));

            }

     

            privateAlgorithm GetMD5Algorithm(string digestAuthHeader)

            {

                var md5Regex = newRegex(@"algorithm=(?<algo>.*)[,]", RegexOptions.IgnoreCase);

                var md5Attribute = md5Regex.Match(digestAuthHeader);

                if (md5Attribute.Success)

                {

                    char[] charSeparator = newchar[] { ',' };

                    string algorithm = md5Attribute.Result("${algo}").ToLower().Split(charSeparator)[0];

     

                    switch (algorithm)

                    {

                        case"md5-sess":

                        case"\"md5-sess\"":

                            returnAlgorithm.MD5sess;

                       

                        case"md5":

                        case"\"md5\"":

                        default:

                            returnAlgorithm.MD5;                   

                    }

                }

     

                thrownewApplicationException("Could not determine Digest algorithm to be used from the server response.");

            }

     

            privatestring ComputeMd5Hash(string input)

            {

                var inputBytes = Encoding.ASCII.GetBytes(input);

                var hash = MD5.Create().ComputeHash(inputBytes);

                var sb = newStringBuilder();

                foreach (var b in hash)

                    sb.Append(b.ToString("x2"));

                return sb.ToString();

            }

     

            publicenumAlgorithm

            {

                MD5 = 0, // Apache Default

                MD5sess = 1 //IIS Default

            }

        }

    }

     

    Sample usage for HTTP GET requests:

    DigestHttpWebRequest request = newDigestHttpWebRequest(username, password);

    HttpWebResponse result = request.GetResponse(uri); 

     

    Sample usage for HTTP POST requests:

    DigestHttpWebRequest request = newDigestHttpWebRequest(username, password);

    request.Method = WebRequestMethods.Http.Post;

    request.ContentType = "text/plain; charset=utf-8";

    request.PostData = Encoding.ASCII.GetBytes(postData);               

    HttpWebResponse result = request.GetResponse(uri); 

     

    Applies to
    Microsoft .NET Framework 1.1
    Microsoft .NET Framework 2.0
    Microsoft .NET Framework 3.0
    Microsoft .NET Framework 3.5
    Microsoft .NET Framework 4.0
    Microsoft .NET Framework 4.5

  • The code is out there

    Corrupción de las claves RSA y la importancia de hacer backup

    Posted over 1 year ago
    by Daniel Mossberg
    • 0 Comments

    Recientemente he trabajado en un caso en el que tras reiniciar un servidor Windows Server 2003, el servicio IIS Admin Service fallaba en el arranque con el siguiente mensaje de error:

      

    Windows could not start the IIS Admin Service on Local Computer. For more information, review the System Event Log. If this is a non-Microsoft service, contact the service vendor, and refer to service-specific error code -2146893813.

     
    Adicionalmente, en el log de eventos de sistema se registraba el siguiente error:

      

    Manuel: Event Type: Error
    Event Source: Service Control Manager
    Event Category: None
    Event ID: 7024
    Date: 23/05/2012
    Time: 11:40:35
    User: N/A
    Computer: IIS6-Server
    Description:
    The IIS Admin Service service terminated with service-specific error 2148073483 (0x8009000B).

     

    El código de error 0x8009000B se corresponde a NTE_BAD_KEY_STATE (“Key not valid for use in specified state”o “Clave no válida para utilizar en el estado especificado” en castellano) e indica un error al utilizar unas claves de encriptación.

    IIS 6.0 cifra algunos campos del fichero XML de configuración (metabase.xml) con información sensible como contraseñas de cuentas de servicio. Para esto, IIS 6.0 crea un contenedor de claves criptográficas durante la instalación del servicio llamado “Microsoft Internet Information Server”. Este contenedor de claves está almacenado físicamente en el fichero %ALLUSERSPROFILE%\Application Data\Microsoft\Crypto\RSA\MachineKeys\c2319c42033a5ca7f44e731bfd3fa2b5_<MachineGuid>. El valor de MachineGuid se puede obtener de la siguiente clave del registro y será distinto en cada servidor: 
      

    [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography]

    "MachineGuid"="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

     
    Con la herramienta certutil podemos volcar cierta información de este contenedor de claves, y entre otras cosas podemos ver que las claves privadas no son exportables, por lo que el fichero metabase.xml cifrado con estas claves únicamente podrá ser descifrado en el mismo servidor donde fue creado. 
      

    C:\WINDOWS\system32>certutil -v -key "Microsoft Internet Information Server"

    Microsoft Strong Cryptographic Provider:

    Microsoft Internet Information Server

    c2319c42033a5ca7f44e731bfd3fa2b5_809cf898-e06e-4673-83e2-6200ef0291cc


    AT_SIGNATURE:

    Key Id Hash(sha1): d0 31 ed 58 81 bf 61 b9 a3 76 99 cb 17 ad ce 88 ff b7 b8 cd

    Container Public Key:

    000030 48 02 41 00 cd c2 bab5 47 a7 2b 2f 57 4b ae

    0010a7 fe 26 9c a2 ed f3 f958 36 df 8f 35 31 1c c0

    002032 2c 35 8c 42 68 84 0dc2 4d dd 9a fd 5d 87 0f

    0030b2 b9 33 d4 1b bc 62 647c bd d7 6c 59 6e c1 85

    0040b3 46 73 2c 59 02 03 0100 01


    Private key is NOT exportable


    AT_KEYEXCHANGE:

    Key Id Hash(sha1): 4f 50 e1 14 9f 63 73 0b ef ae 7c 34 e5 1d 6b d2 eb 1b 59 27

    Container Public Key:

    000030 48 02 41 00 a3 aa e0ee 9f 45 e9 b4 6c 4d 11

    001044 96 39 f8 f6 51 ec 9014 60 1f 57 6e 10 d2 f4

    002069 4c c1 1f 09 f5 69 e0fd 75 67 40 00 0d a0 4a

    00307d c3 b5 a1 40 b9 20 a65a 3e 1e e9 28 bd aa 2c

    00406e 62 64 c9 61 02 03 0100 01


    Private key is NOT exportable


    CertUtil: -key command completed successfully.

     
    IIS 6.0 permite generar backups de la metabase exportables/importables a cualquier servidor, para lo que se utiliza una contraseña en lugar de las claves RSA de máquina para cifrar la metabase. Tener un backup de la configuración de IIS protegido con contraseña puede resultar muy útil como veremos más adelante. 
      

    clip_image001_thumb

     
    Volviendo al problema original, el error 0x8009000B al arrancar el servicio IIS Admin Service típicamente se debe a una corrupción del contenedor de claves de IIS, por lo que el servicio no es capaz de descifrar los campos relevantes al leer el fichero de configuración y por consecuencia fallan en el arranque.

    Llegados a esta situación, básicamente tenemos tres opciones, que por orden de preferencia son:  

     
    1) Restaurar un backup de system state en el que se restauraría una copia intacta del fichero contenedor de claves c2319c42033a5ca7f44e731bfd3fa2b5_<MachineGuid> lo que permitiría volver a arrancar el servicio de IIS Admin Service.
    2) Reinstalar IIS y reconfigurarlo desde un backup de la metabase protegido por contraseña que podamos importar después de la reinstalación.   

    3) Reinstalar IIS y reconfigurarlo manualmente desde cero.

          

    PROCESO DE REINSTALACIÓN Y RESTAURACIÓN DEL CONTENEDOR DE CLAVES
    En caso que fuera necesaria la reinstalación de IIS, los pasos a seguir son los siguientes:


    1) Desinstalar el servicio de IIS desde los componentes de Windows.
      

    2) Eliminar el fichero contenedor de claves de “Microsoft Internet Information Server” en la ruta“%ALLUSERSPROFILE%\Application Data\Microsoft\Crypto\RSA\MachineKeys\c2319c42033a5ca7f44e731bfd3fa2b5_<MachineGuid>”.

    3) Reinstalar IIS.

    4) Importar el backup de la metabase protegido por contraseña.

    5) Añadir las cuentas configuradas como identidad de los application pool al grupo de seguridad IIS_WPG (IIS Worker Process Group) de nuevo, dado que el grupo de seguridad se vuelve a crear tras la reinstalación.

     
    Si tenemos varios servidores configurados idénticamente, técnicamente es posible importar un backup de la metabase que ha sido exportado desde otro servidor distinto. No obstante, esto implica tener que modificar manualmente algunas cosas adicionales como sincronizar las cuentas IUSR_<serverName> e IWAM_<serverName> y sus contraseñas, o modificar los bindings de los sitios web en caso de que escuchen por IPs distintas en los distintos servidores de la granja.

    En definitiva, haced backup del system state y de la metabase regularmente, y espero que nunca tengáis que hacer uso de este post.   

     
    - Daniel Mossberg

  • The code is out there

    Modelos de programación en ASP.NET: Web Forms, MVC y Web Pages

    Posted over 1 year ago
    by Daniel Mossberg
    • 4 Comments

    Recientemente he publicado una serie de artículos en MSDN España abordando una pregunta que recibimos con frecuencia. ¿Cuáles son las diferencias entre los tres modelos de programación de ASP.NET y en que situaciones debo elegir uno de ellos sobre los otros?

     

    Estos son los enlaces a la serie completa:

     

    ASP.NET Web Forms, MVC o Web Pages – Introducción

    http://msdn.microsoft.com/es-es/asp.net/hh984851

     

    Parte 1 – ASP.NET Web Forms

    http://msdn.microsoft.com/es-es/asp.net/hh984854

     

    Parte 2 – ASP.NET MVC

    http://msdn.microsoft.com/es-es/asp.net/hh984855

     

    Parte 3 – ASP.NET Web Pages

    http://msdn.microsoft.com/es-es/asp.net/hh984856

     

    Espero que os resulte interesante.

    - Daniel Mossberg

  • The code is out there

    HttpException: An error occurred while attempting to impersonate

    Posted over 3 years ago
    by Daniel Mossberg
    • 2 Comments

    Pasados más de tres meses desde mi último post, ya es momento de retomarlo. Hoy voy a escribir sobre un problema con el que me he encontrado en varias ocasiones en los últimos meses. Básicamente, el problema se produce cuando tenemos una aplicación ASP.NET configurada para impersonar al usuario autenticado, y en algún punto de la aplicación se trata de impersonar programáticamente a un usuario distinto. En estas circunstancias, la aplicación va a fallar con el siguiente error:

     

    Exception Details:

    System.Web.HttpException: An error occurred while attempting to impersonate. Execution of this request cannot continue.

     

    Stack Trace:

    [HttpException (0x80004005): An error occurred while attempting to impersonate. Execution of this request cannot continue.]

    System.Web.ImpersonationContext.GetCurrentToken() +8845961

    System.Web.ImpersonationContext.get_CurrentThreadTokenExists() +58

    System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +193

    System.Web.ApplicationStepManager.ResumeSteps(Exception error) +501

    System.Web.HttpApplication.System.Web.IHttpAsyncHandler.BeginProcessRequest(HttpContext context, AsyncCallback cb, Object extraData) +123

    System.Web.HttpRuntime.ProcessRequestInternal(HttpWorkerRequest wr) +379

    …

     

    Básicamente, el problema radica en que al llamar al método Impersonate (o llamando directamente a la API LogonUser que es la que se termina llamando desde Impersonate), se intenta acceder al token del proceso (por ejemplo, w3wp.exe), y la cuenta del usuario autenticado (el que está siendo impersonado debido a la configuración del web.config) no tiene permiso para acceder a dicho token.

     

    Por lo tanto, para poder impersonar una cuenta distinta programáticamente, necesitamos deshacer la impersonación inicial antes de impersonar una cuenta distinto. Para ello, podemos llamar a la API RevertToSelf para deshacer la impersonación antes de llamar a LogonUser.

     

    En el siguiente ejemplo se ilustra cómo podríamos hacerlo:

     

    const int LOGON32_PROVIDER_DEFAULT = 0;

    const int LOGON32_LOGON_INTERACTIVE = 2;

    const int SecurityImpersonation = 2;

     

    int win32ErrorNumber = 0;

     

    //Mostramos por pantalla la identidad inicial (la impersonada mediante en el web.config):

    Response.Write("Identidad inicial: " + WindowsIdentity.GetCurrent().Name.ToString() + "<BR>");

     

    //Guardamos esa identidad inicial para poder restaurarlo posteriormente

    WindowsIdentity _initialIdentity = WindowsIdentity.GetCurrent();

     

    //Llamamos a la API "RevertToSelf" para deshacer la impersonación configurada en el web.config:

    RevertToSelf();

     

    //Volvemos a mostrar la identidad, y ahora vemos que ha cambiado a la identidad del application

    //pool: (NETWORK SERVICE) por defecto.

    Response.Write("Después de llamar a RevertToSelf: " + WindowsIdentity.GetCurrent().Name.ToString() + "<BR>");

     

    //Realizamos la impersonación deseada.

    IntPtr _tokenHandle = IntPtr.Zero;

    IntPtr _dupeTokenHandle = IntPtr.Zero;

     

    string _domainname = "CONTOSO";

    string _username = "demo";       

    string _password = "P@ssw0rd!";

     

    if (!LogonUser(_username, _domainname, _password, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, out _tokenHandle))

    {

        win32ErrorNumber = System.Runtime.InteropServices.Marshal.GetLastWin32Error();

        throw new Exception(win32ErrorNumber.ToString());

    }

     

    if (!DuplicateToken(_tokenHandle, SecurityImpersonation, out _dupeTokenHandle))

    {

        win32ErrorNumber = System.Runtime.InteropServices.Marshal.GetLastWin32Error();

     

        CloseHandle(_tokenHandle);

        throw new Exception(win32ErrorNumber.ToString());

    }

     

    System.Security.Principal.WindowsIdentity newId = new System.Security.Principal.WindowsIdentity(_dupeTokenHandle);

    WindowsImpersonationContext _impersonatedUser = newId.Impersonate();

     

    //Mostramos por pantalla la identidad impersonada

    Response.Write("Después de impersonar: " + WindowsIdentity.GetCurrent().Name.ToString() + "<BR>");

     

    //Desahacemos la impersonación cuando ya no lo necesitemos.

    _impersonatedUser.Undo();

     

    //Mostramos por pantalla la identidad, que otra vez será la del application pool

    Response.Write("Deshacer impersonación: " + WindowsIdentity.GetCurrent().Name.ToString() + "<BR>");

     

    //Volvemos a impersonar con la identidad inicial (la del web.config) que hemos mantenido referenciada

    //con la variable _impersonatedUser:

    _impersonatedUser = _initialIdentity.Impersonate();

     

    //Por último volvemos a mostrar la identidadMostramos por pantalla la identidad

    Response.Write("Volver a impersonación inicial: " + WindowsIdentity.GetCurrent().Name.ToString() + "<BR>");

     

    Tras ejecutar el código anterior, el resultado emitido por pantalla sería el siguiente (en mi ejemplo):

     

    Identidad inicial: CONTOSO\daniel
    Después de llamar a RevertToSelf: NT AUTHORITY\NETWORK SERVICE
    Después de impersonar: CONTOSO\demo
    Deshacer impersonación: NT AUTHORITY\NETWORK SERVICE
    Volver a impersonación inicial: CONTOSO\daniel

     

    Estas son las signatures de C# para llamar a las APIs del ejemplo mediante platform invoke:

     

    [DllImport("advapi32.dll", SetLastError = true)]

    public static extern bool LogonUser(

        string lpszUsername,

        string lpszDomain,

        string lpszPassword,

        int dwLogonType,

        int dwLogonProvider,

        out IntPtr phToken);

     

    [DllImport("advapi32.dll", SetLastError = true)]

    static extern bool RevertToSelf();

     

    [DllImport("advapi32.dll", SetLastError = true)]

    public extern static bool DuplicateToken(

        IntPtr ExistingTokenHandle,

        int SECURITY_IMPERSONATION_LEVEL,

        out IntPtr DuplicateTokenHandle);

     

    [DllImport("kernel32.dll", SetLastError = true)]

    [return: MarshalAs(UnmanagedType.Bool)]

    static extern bool CloseHandle(IntPtr hObject);

     

    Espero que os sea de utilidad.

    - Daniel Mossberg

  • The code is out there

    Problemas al subir ficheros a una aplicación ASP.NET

    Posted over 3 years ago
    by Daniel Mossberg
    • 0 Comments

    Cuando subimos ficheros a una aplicación ASP.NET, dependiendo del tamaño del fichero y la velocidad de la conexión del cliente, ocasionalmente la subida del fichero puede fallar con el mensaje de error en el cliente de Internet Explorer no puede mostrar la página web, o con alguna de las siguientes excepciones (dependiendo de cómo esté configurado IIS, habitualmente este detalle de la excepción únicamente lo podremos ver en el log de eventos NT del servidor):

     

    HttpException: Request timed out.

    HttpException: Maximum request length exceeded.

     

    En estos casos, los parámetros que debéis configurar adecuadamente considerando el tamaño el tiempo de transmisión del fichero son los siguientes:

     

    <httpRuntime executionTimeout="600" maxRequestLength="61440" />

     

    El parámetro de executionTimeout determina el tiempo en segundos que puede ejecutarse una páginas ASP.NET antes de que se lanza una excepción de time out. Por otro lado, el parámetro maxRequestLength determina el tamaño total máximo en bytes de las peticiones HTTP. Debemos configurar estas secciones acorde con los tiempos máximos de carga y tamaños máximos de fichero que queremos que soporte nuestra aplicación.

     

    Los parámetros de la sección <httpRuntime> se pueden configurar a nivel de web.config de aplicación, e incluso a nivel de una página específica de la siguiente manera:

     

    <?xml version="1.0"?>

    <configuration>

      ...

      <location path="Default.aspx">

        <!--

          Aquí la configuración específica

          para la página Default.aspx del

          directorio virtual al que aplica

          este web.config.

        -->

     

        <system.web>

          <httpRuntime

            executionTimeout="600"

            maxRequestLength="61440" />

        </system.web>

      </location>

     

    </configuration>

     

    Esto último es recomendable, dado que aumentar el executionTimeout de todas las páginas de una aplicación puede tener consecuencias negativas. En general, los timeout se deben mantener con valores razonablemente bajos, precisamente para que cuando se produzca una situación de bloqueo, se lanze la excepción y se finalice el procesamiento de la página en poco tiempo de forma que se libere el thread y se puedan atender otras peticiones. Con timeouts elevados se podrían llegar a agotar todos los threads en situaciones de bloqueo, por tardar demasiado en ser liberados.

     

    Por último, si una vez habiendo configurado nuestra aplicación con valores adecaudos, la carga de ficheros sigue fallando con errores HTTP 400 en los logs de IIS y errores de Timer_EntityBody en los logs de HTTP.sys (HTTPERR), probad a desinstalar la funcionalidad de SNP (Scalable Networking Pack) en el servidor web siguiente los instrucciones del siguiente artículo:

     

    An update to turn off default SNP features is available for Windows Server 2003-based and Small Business Server 2003-based computers

    http://support.microsoft.com/default.aspx?scid=kb;EN-US;948496

     

    Estos cambios requieren reincio del sistema operativo para entrar en vigor.

     

    Espero que os haya sido de utilidad.

    - Daniel Mossberg

     

  • The code is out there

    Cómo reutilizar el código de una biblioteca de clases .NET desde una aplicación Silverlight

    Posted over 3 years ago
    by Daniel Mossberg
    • 2 Comments

    Existe cierta confusión con la compatibilidad entre binarios de Silverlight y binarios de .NET, y a más de uno le pilla por sorpresa que en un proyecto de Visual Studio de una aplicación Silverlight, no se pueden referenciar bibliotecas de clases (class library) desarrolladas en .NET de escritorio (para diferenciarlo del .NET de Silverlight). El motivo es que el runtime y la biblioteca de clases de .NET para Silverlight es un subconjunto del runtime y la biblioteca de clases de .NET de escritorio, y por lo tanto no son directamente compatibles.

     

    Se puede migrar el código de las clases .NET a clases .NET para Silverlight, y en ocasiones puede ser tan sencillo como recompilar el mismo código fuente (por ejemplo, como una Silverlight Class Library). En otras ocasiones esta migración no es directa, dado que la biblioteca de clases de Silverlight no contiene todas las clases que la biblioteca de clases del Framework .NET.

     

    .NET Framework Class Library

    http://msdn.microsoft.com/en-us/library/ms229335.aspx

     

    .NET Framework Class Library for Silverlight

    http://msdn.microsoft.com/en-us/library/cc838194.aspx

     

    Supongamos que queremos reutilizar unas bibliotecas de clases que manejan objetos DataSet, y dado que .NET para Silverlight (hasta la versión Silverlight 4 RC) no contiene el namespace System.Data, no podemos simplemente recompilar el código fuente (fallaría la compilación dado que no estarían implementada ninguna clase DataSet).

     

    Una posible implementación que nos permite reutilizar al máximo nuestras bibliotecas de clases .NET desde aplicaciones Silverlight sería implementar una interfaz, expuesta como un web service, y consumir dicho web service desde la aplicación Silverlight.

     

    De esta manera, todo el código de .NET de escritorio se ejecutará en el servidor web, y la aplicación Silverlight que se ejecutará en el navegador del cliente únicamente representará la capa de presentación.

     

    Os muestro un pequeño ejemplo en C# como prueba de concepto. En este ejemplo, el web service está implementado como un web service de ASP.NET 2.0 (*.asmx), pero considerad la posibilidad de implementarlo como un web service WCF (Windows Communication Foundation), que es la siguiente generación de web services de .NET.

     

    Supongamos que tenemos la siguiente clase en nuestra biblioteca de lógica de negocio. Esta es la clase cuyo código nos gustaría reutilizar en Silverlight, pero dado que hace uso de System.Data no podemos recompilar el código sin más.         

     

    using System;

    using System.Data;

     

    namespace SilverlightTest.Web

    {

        public class BusinessLogic

        {

            public DataSet getCustomerData(int customerID)

            {

                DataSet dataSet = new DataSet();

                DataTable dataTable = new DataTable();

     

                dataTable.Columns.Add("Columna 1");

                dataTable.Columns.Add("Columna 2");

     

                dataTable.Rows.Add(new object[] { "celda 0-0", "celda 0-1" });

                dataTable.Rows.Add(new object[] { "celda 1-0", "celda 1-1" });

                dataTable.Rows.Add(new object[] { "celda 2-0", "celda 2-1" });

                dataTable.Rows.Add(new object[] { "celda 3-0", "celda 3-1" });

     

                dataSet.Tables.Add(dataTable);

     

                return dataSet;           

            }

        }

    }

     

    Para solventar este problema, podemos crear un web service que exponga el método getCustomerData. En este caso, para hacerlo compatible con Silverlight, convertiremos el objeto DataSet en uno o varios arrays bidimensionales (uno por cada DataTable del DataSet) antes de enviar la respuesta al cliente Silverlight:

     

    using System;

    using System.Data;

    using System.Web;

    using System.Web.Services;

    using SilverlightTest.Web;

     

    [WebService]

    public class SilverlightInterface : System.Web.Services.WebService

    {

     

        [WebMethod]

        public object[][] getCustomerData(int customerID)

        {

            //Utilizamos la lógica de la biblioteca de clases de .NET de escritorio

            BusinessLogic businessLogic = new BusinessLogic();

            DataSet dataSet = businessLogic.getCustomerData(customerID);

     

            int x = dataSet.Tables[0].Rows.Count;

            int y = dataSet.Tables[0].Columns.Count;

     

            //Declaramos y demensionamos el Array

            object[][] dataArray = new object[x][];

     

            for (int i = 0; i < x; i++)

            {

                dataArray[i] = new object[y];

            }

     

            //Rellenamos el Array con los datos del DataSet

            for (int i = 0; i < x; i++)

            {

                for (int j = 0; j < y; j++)

                {

                    dataArray[i][j] = dataSet.Tables[0].Rows[i][j];

                }

            }

     

            return dataArray;

        }

    }

     

    Por último, desde el cliente Silverlight consumimos el web service de la siguiente manera tras haber incluido la referencia web (que hemos llamado BusinessLogicWS):

     

    public partial class SilverlightPage : UserControl

    {

        public SilverlightPage()

        {

            InitializeComponent();       

     

            //Creamos el objeto proxy para interactuar con el web service

            BusinessLogicWS.SilverlightInterfaceSoapClient wsProxy =

                new BusinessLogicWS.SilverlightInterfaceSoapClient();

     

            wsProxy.getCustomerDataCompleted

                += new EventHandler

                    <BusinessLogicWS.getCustomerDataCompletedEventArgs>

                    (getCustomerDataCompletedHandler);

     

            int CustomerID = 1234;

     

            //Realizamos la llamada asíncrona al web service       

            wsProxy.getCustomerDataAsync(CustomerID);

        }

     

        void getCustomerDataCompletedHandler(object sender,

            BusinessLogicWS.getCustomerDataCompletedEventArgs e)

        {

            //Obtenemos los datos de la respuesta del web service

            ObservableCollection<SilverlightTest.BusinessLogicWS.ArrayOfAnyType>

                data = e.Result;           

     

            //Hacemos algo con esos datos

            for (int i=0; i<data.Count; i++)

            {

                for (int j = 0; j < data[i].Count; j++ )

                {

                    Debug.WriteLine(data[i][j].ToString());               

                }

            }

        }

    }

     

    De esta forma, hemos construido una interfaz entre la aplicación Silverlight y nuestra biblioteca de clases .NET, pudiendo reutilizar el código de las bibliotecas de clases .NET y solventando los problemas de incompatibilidades entre tipos de datos.

     

    Hasta el próximo post,

    - Daniel Mossberg

  • The code is out there

    Cómo capturar volcados de crash con Debug Diagnostics Tool

    Posted over 3 years ago
    by Daniel Mossberg
    • 0 Comments

    Este post es la continuación de Cuándo y cómo capturar volcados de memoria en modo Crash

     

    La herramienta Debug Diagnostics Tool se puede descargar desde aquí, tanto la versión de 32-bit como la de 64-bit. En el momento de escribir este post, la herramienta está en la versión 1.1 y está soportada para los siguientes sistemas operativos:

     

    ·         Windows 2000 Server

    ·         Windows Server 2003

    ·         Windows XP

     

    Es decir, si pretendemos analizar un problema en Windows Server 2008, Windows Server 2008 R2, Windows Vista o Windows 7 es recomendable utilizar otras herramientas, aunque algunas funcionalidades de Debug Diagnostics pueden funcionar en estos sistemas operativos.

     

    Nota

    En el caso de los sistemas operativos de 64-bit, es conveniente instalar la versión de DebugDiag en función del proceso que se va a monitorizar. Es decir, si el proceso es de 32-bit, es mejor utilizar la versión de 32-bit de DebugDiag al margen de que Windows sea de 64-bit (de esta forma nos quitamos del medio la capa WOW64 que a veces dificulta la depuración).

     

    DebugDiag nos permite capturar volcados de crash mediante las reglas de crash. Las reglas de crash de DebugDiag permiten adjuntar un depurador a cualquier proceso y configurarlo para que genere volcados en el momento que se produzca una determinada excepción o cuando se finalice el proceso. Estos son los escenarios más comunes:

     

    Volcados de crash de finalización de un proceso

    Volcados de excepciones .NET

     

    VOLCADOS DE CRASH DE FINALIZACIÓN DE UN PROCESO

    Este es el escenario más común para capturar volcados de crash, y consiste en generar un volcado de memoria en el momento que se produce la excepción no manejada que provoca la finalización del proceso.

    Para configurar una regla de crash de estas características debemos posicionarnos en la pestaña [Rules] y posteriormente pulsar sobre el botón  [Add Rule…]. Posteriormente seguiremos estos pasos:

     

    1)      Seleccionar la opción [Crash]

    2)      Configurar a que proceso(s) o aplicación(es) adjuntaremos el depurador mediante alguna de las alternativas que nos proporciona la herramienta (en muchas ocasiones hay varias opciones válidas para conseguir el mismo resultado):

     

    a)      All active IIS/COM+ related processes: Adjuntará el depurador a todos los procesos INETINFO.EXE, W3WP.EXE y DLLHOST.EXE que se ejecuten en la máquina. De esta forma obtendremos datos de cualquier crash que se produzca en un proceso relacionado con IIS.

    b)      A specific process: Adjuntará el depurador a un proceso específico (un determinado PID) o a todos los procesos con el mismo nombre de ejecutable (por ejemplo W3WP.EXE), en función de sí seleccionamos la opción This process instance only.

    c)      A specific MTS/COM+ application: Adjuntará el depurador al proceso DLLHOST.EXE que aloje la aplicación COM+ que indiquemos.

    d)      Web application pool: Adjuntará el depurador al proceso W3WP.EXE que aloje el application pool que indiquemos.

    e)      NT Service: Adjuntará el depurador al proceso que aloje al servicio de Windows que indiquemos, por ejemplo SVCHOST.EXE.

    3)      En la pantalla de configuración avanzada dejaremos la configuración predeterminada, de forma que no tome ninguna acción cuando se produzcan excepciones de first chance:

    clip_image002

    4)      Por último debemos indicar el nombre de la regla, la ubicación donde volcará los datos generados y activarla para que empiece a capturar datos. En función del escenario, estos datos pueden ser de gran tamaño por lo que conviene elegir una unidad que no tenga problemas de espacio.

     

    VOLCADOS DE EXCEPCIONES .NET

    En muchas ocasiones necesitamos analizar volcados de determinadas excepciones .NET, para examinar el estado de los objetos implicados en el momento de producirse la excepción. A partir de .NET 2.0, el CLR hace una gran labor registrando información sobre las excepciones no manejadas en el log de eventos NT, pero en ocasiones no es suficiente y necesitamos los volcados de memoria. Para configurar una regla de crash de estas características debemos posicionarnos en la pestaña [Rules] y posteriormente pulsar sobre el botón  [Add Rule…]. Posteriormente seguiremos estos pasos:

     

    1)      Seleccionar la opción [Crash]

    2)      Configurar a que proceso(s) o aplicación(es) adjuntaremos el depurador mediante alguna de las alternativas que nos proporciona la herramienta. Habitualmente nos adjuntaremos a uno o varios procesos W3WP.EXE mediante alguno de los mecanismo que nos proporciona la herramienta:

     

    a.      All active IIS/COM+ related processes: Adjuntará el depurador a todos los procesos INETINFO.EXE, W3WP.EXE y DLLHOST.EXE que se ejecuten en la máquina. De esta forma obtendremos datos de cualquier crash que se produzca en un proceso relacionado con IIS.

    b.      A specific process: Adjuntará el depurador a un proceso específico (un determinado PID) o a todos los procesos con el mismo nombre de ejecutable (por ejemplo W3WP.EXE), en función de sí seleccionamos la opción This process instance only.

    c.       A specific MTS/COM+ application: Adjuntará el depurador al proceso DLLHOST.EXE que aloje la aplicación COM+ que indiquemos.

    d.      Web application pool: Adjuntará el depurador al proceso W3WP.EXE que aloje el application pool que indiquemos.

    e.      NT Service: Adjuntará el depurador al proceso que aloje al servicio de Windows que indiquemos, por ejemplo SVCHOST.EXE.

    3)      En la pantalla de configuración avanzada pulsamos sobre el botón [Exceptions] y en la ventana de First Chance Exception Configuration le damos a [Add Exception…]. Esto abre una nueva ventana donde configuraremos la excepción específica que queremos monitorizar. Debemos seleccionar el tipo de excepción adecuado en la columna izquierda CLR (.NET) Exception y posteriormente indicar el tipo de excepción y la acción a tomar. Es importante tener en cuenta que el campo [.NET Exception Type] es case sensitive, por lo que debemos poner el nombre exacto de la excepción con las mayúsculas y minúsculas adecuadas. La acción a realizar cuando se produzca una excepción como la configurada será generar un Full Userdump, y limitaremos esta acción a un máximo de 10 volcados:

    clip_image003


    4)      Una vez configurada la excepción adecuada, pulsamos [OK] à [Save & Close] à [Next], y finalizamos indicando el nombre de la regla, la ubicación donde volcará los datos generados y activando la regla para que empiece a capturar datos

     

    En una serie de próximos posts, cubriré algunas técnicas para introducirse en el análisis de volcados de memoria.

     

    Happy hacking,

    - Daniel Mossberg

  • The code is out there

    Cómo capturar una traza de red circular con Network Monitor

    Posted over 3 years ago
    by Daniel Mossberg
    • 0 Comments

    En ocasiones, necesitamos capturar trazas de red durante un periodo de tiempo prolongado, por ejemplo para esperar hasta que se produzca un determinado comportamiento aleatorio que pretendemos investigar. Para estos escenarios puede ser útil capturar una traza de red circular, de forma que únicamente se guarden los últimos n MB capturados, y los datos anteriores se vayan desechando automáticamente.

     

    De esta forma, la traza puede estar capturando datos durante días, y en el momento que se produzca el problema, paramos la traza que contendrá el tráfico de red relevante que pretendíamos investigar en un fichero de un tamaño manejable.

     

    Es importante configurar un tamaño de buffer lo suficientemente grande para tener un margen desde que se reproduce el problema hasta que se para la traza, de forma que esta contenga el periodo de tiempo que se pretende investigar. El problema de las trazas circulares es que si se para la traza demasiado tarde, es posible que los datos que se pretendían investigar ya hayan sido desechados.

     

    Network Monitor, permite capturar trazas circulares mediante la utilidad de línea de comandos nmcap.exe. La sintaxis para capturar una traza circular de 200 MB sería la siguiente:

     

    C:\...\Microsoft Network Monitor 3>nmcap.exe /network * /capture /file netTrace.cap:200M

    Netmon Command Line Capture (nmcap) 3.3.1641.0

     

    Saving info to:

    C:\Program Files\Microsoft Network Monitor 3\netTrace.cap - using circular buffer of size 200.00 MB.

     

    ATTENTION: Conversations Enabled: consumes more memory (see Help for details)

     

    Exit by Ctrl+C

     

    Capturing   | Received: 411 Pending: 0 Saved: 411 Dropped: 0 | Time: 17 seconds.

     

    En el momento que se haya producido el problema, finalizamos la captura presionando Ctrl + C.

     

    Hasta el próximo post,

    - Daniel Mossberg

  • The code is out there

    ¿Qué son las excepciones de first chance y second chance?

    Posted over 3 years ago
    by Daniel Mossberg
    • 2 Comments

    Cuando adjuntamos un depurador a un proceso y se produce una excepción, el depurador es el primero en ser notificado de esa excepción. El depurador puede manejarla, o simplemente dejar que la ejecución siga su curso, en cuyo caso el siguiente en ser notificado de la excepción es la propia aplicación. Por lo tanto, esta primera oportunidad que tiene el depurador para capturar una excepción se llama first chance. Examinando las excepciones con un depurador como WinDbg, podríamos ver algo parecido a esto:

    (1f68.140c): CLR exception - code e0434f4d (first chance)

    First chance exceptions are reported before any exception handling.

    This exception may be expected and handled.

    KERNELBASE!RaiseException+0x39:

    000007fe`fcfcaa7d 4881c4c8000000  add     rsp,0C8h

     

    Examinando los detalles de la excepción .NET en cuestión (CLR Exception), podríamos ver que el depurador está capturando la siguiente excepción System.Web.HttpException:

     

    0:032> !printexception

    Exception object: 0000000155a173b8

    Exception type: System.Web.HttpException

    Message: The file '/myApp/myUserControl.ascx' does not exist.

    InnerException: <none>

    StackTrace (generated):

    <none>

    StackTraceString: <none>

    HResult: 80004005

     

    En esta ocasión, la aplicación ASP.NET maneja la excepción adecuadamente, y el problema no pasa a mayores. No obstante, se puede dar la circunstancia de que la aplicación no maneje la excepción, o no pueda manejarla, en cuyo caso se lanzaría una excepción de second chance. Las excepciones de second chance son la segunda y última oportunidad de capturar una excepción. En la mayoría de los casos, cuando se produce una excepción de second chance, la ejecución el proceso finaliza inmediatamente, se produce un crash.

     

    En el siguiente ejemplo, vemos como se produce una excepción de Stack Overflow, y al no ser manejada por la aplicación (las excepciones de Stack Overflow particularmente, no se pueden manejar) se lanza primero una excepción de first chance, y posteriormente en la misma instrucción de ensamblador, se lanza la excepción de second chance. que provoca la finalización del proceso:

     

    (1f60.1c0c): Stack overflow - code c00000fd (first chance)

    First chance exceptions are reported before any exception handling.

    This exception may be expected and handled.

    eax=00032000 ebx=7ffd9000 ecx=0001f208 edx=7c82860c esi=00000000 edi=00000000

    eip=00401237 esp=000378b4 ebp=000378bc iopl=0         nv up ei pl nz na pe nc

    cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010206

    StackOverflow!_chkstk+0x27:

    00401237 8500            test    dword ptr [eax],eax  ds:0023:00032000=00000000

     

    (1f60.1c0c): Stack overflow - code c00000fd (!!! second chance !!!)

    eax=00032000 ebx=7ffd9000 ecx=0001f208 edx=7c82860c esi=00000000 edi=00000000

    eip=00401237 esp=000378b4 ebp=000378bc iopl=0         nv up ei pl nz na pe nc

    cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010206

    StackOverflow!_chkstk+0x27:

    00401237 8500            test    dword ptr [eax],eax  ds:0023:00032000=00000000

     

    Dependiendo del tipo de escenarios que estemos investigando, puede ser relevante analizar las excepciones de first chance, las de second chance, o las dos.  

     

    Hasta el próximo post,

    - Daniel Mossberg

  • The code is out there

    Cuándo y cómo capturar volcados de memoria en modo Crash

    Posted over 3 years ago
    by Daniel Mossberg
    • 0 Comments

    Habitualmente, cuando adjuntamos un depurador en modo crash a un proceso, queremos que el depurador que realice una determinada acción cuando se produzca un determinado evento. Básicamente hay dos escenario típicos en los que adjuntaríamos un depurador en modo crash. Para generar volcados de memoria cuando se produzca una determinada excepción de first chance, o para generar volcados de memoria cuando se finalice un proceso inesperadamente al producirse una excepción de second chance. Para más información sobre tipos de excepciones, leer el post ¿Qué son las excepciones de first chance y second chance?

     

    Podemos utilizar dos herramientas distintas para capturar este tipo de volcados, Debug Diagnostics o Adplus. Las herramientas son funcionalmente equivalentes, aunque cada una tiene sus ventajas y sus desventajas. En los siguientes posts encontraréis la información detallada sobre cómo generar los volcados con estas herramientas:

     

    ·         Cómo capturar volcados de crash con Debug Diagnostics Tool

    ·         Cómo capturar volcados de crash con Adplus (próximamente)

     

    Hasta pronto,

    - Daniel Mossberg

  • The code is out there

    Elevado consumo de memoria del proceso DbgHost.exe de DebugDiag

    Posted over 3 years ago
    by Daniel Mossberg
    • 0 Comments

    En algunos entornos, cuando se utiliza Debug Diagnostics para monitorizar excepciones de .NET, se dispara el consumo de memoria del proceso DbgHost.exe. El motivo de la pérdida de memoria es un bug en la extensión del depurador SOS.dll que se instala con el Framework .NET, concretamente al ejecutar el comando !DumpObj de dicha extensión.

     

    Para solucionar este problema, podemos modificar el fichero de script que genera DebugDiag de forma que no utilice el comando !DumpObj sino !PrintException que no adolece del mismo problema. Por lo tanto, los pasos a seguir son los siguientes:

     

    1)      Borrar todas las reglas de DebugDiag existentes.

    2)      Volver a crear las regla para monitorizar excepciones .NET, pero sin activarla al final del proceso.

    3)      Ir a la carpeta de scripts de DebugDiag, la ruta podría ser algo parecido a esto: C:\Program Files\DebugDiag\scripts

    4)      En esa carpeta encontraréis algún fichero cuyo nombre empiece por CrashRule_...*.vbs con el nombre de la regla que habéis creado. Abrid este fichero con algún editor.

    5)      Buscad la función GetCLRExceptionType, que tendrá un aspecto parecido a este:

     

    Function GetCLRExceptionType(ByVal ExceptionObjHexAddr, ByVal bInnerException)

        Dim Output, Lines, i

     

       If Debugger.IsClrExtensionMissing Then

          WriteToLog "Unable to determine CLR exception type - extension dll could" &

             "not be loaded."

       Else

          Output = Debugger.Execute("!DumpObj " & ExceptionObjHexAddr)

          Lines = Split(Output, Chr(10))

          For i = 0 To UBound(Lines)     

             If bInnerException Then

                If Len(Lines(i) >= 68) Then

                   If InStr(Lines(i), "_innerException") <> 0 Then

                      GetCLRExceptionType = GetCLRExceptionType("0x" & Mid(Lines(i), _

                         60, 8), False)

                      Exit For

                   End If

                End If

             ElseIf Len(Lines(i)) >= 7 Then

                If InStr(Lines(i), "Name: ") = 1 Then

                   GetCLRExceptionType = Mid(Lines(i), 7)

                   Exit For

                End If

             End If

          Next

       End If

    End Function

     

    6)      Eliminad dicha función y sustituidla por esta otra:

     

    Function GetCLRExceptionType(ByVal ExceptionObjHexAddr, ByVal bInnerException)

        Dim Output, Lines, i

     

       If Debugger.IsClrExtensionMissing Then

          WriteToLog "Unable to determine CLR exception type - extension dll could" &

             "not be loaded."

       Else

          Output = Debugger.Execute("!PrintException")

          Lines = Split(Output, Chr(10))

          For i = 0 To UBound(Lines)     

             If bInnerException Then

                If InStr(Lines(i), "_innerException") <> 0 Then

                   Tokens = Split(Lines(i), " ")

                   For j = 0 To UBound(Tokens)

                      If Len(Tokens(j)) = 8 Then                                   

                         GetCLRExceptionType = GetCLRExceptionType(Tokens(j), False)

                         Exit For

                      End If

                   Next

                End If

             ElseIf Len(Lines(i)) >= 7 Then

                If InStr(Lines(i), "Exception type:") = 1 Then

                   GetCLRExceptionType = Mid(Lines(i), 17)

                   WriteToLog(Lines(i))

                   Exit For

                End If

             End If

          Next

       End If

    End Function

     

    7)      Guardad los cambios en el fichero CrashRule_...*.vbs volved a DebugDiag y en la pestaña Rules y activad la regla.


    clip_image002

     

    Realizando estos pases resolveréis el problema de consumo de memoria en DbgHost.exe.

     

    Hasta el próximo post.

    - Daniel Mossberg

  • The code is out there

    Porque no se reflejan inmediatamente los cambios en aplicaciones ASP.NET

    Posted over 4 years ago
    by Daniel Mossberg
    • 0 Comments

    Esta es una problemática relativamente común. Por ejemplo, actualizamos un ensamblado (DLL) de nuestra aplicación ASP.NET, y cuando la probamos vemos que todavía no se han reflejado los cambios y que la aplicación se comporta igual que antes del cambio. ¿Por qué no se reflejan los cambios inmediatamente?

     

    ASP.NET trabaja con application domains para proporcionar aislamiento entre aplicaciones alojadas en un mismo proceso. El application domain contiene todos los datos relacionados con una aplicación en particular, que son (simplificando): todos los ensamblados específicos de la aplicación, un objeto HttpRuntime y un objeto Cache. Un application domain se puede descargar del proceso que lo aloja, pero un ensamblado no se puede descargar del application domain que lo ha cargado.

     

    Por lo tanto, cuando el runtime de .NET (CLR) detecta, por ejemplo, que algún ensamblado de la carpeta ~/bin de la aplicación ha sido modificado, carga un nuevo application domain con la última versión del ensamblado, y el application domain antiguo se descarga del proceso en cuanto haya terminado de servir todas las peticiones en curso.

     

    Cada vez que se reinicia el application domain, todos los ensamblados tienen que volver a cargarse (previamente haciendo el shadow copy), el código vuelve a compilarse por el JIT-compiler, y la cache y las variables de sesión se reinician. El proceso de reinicio del application domain es bastante costoso, por lo que conviene evitar que se produzca cuando la aplicación web tiene mucha carga de usuarios.

     

    ASP.NET utiliza la API Win32 ReadDirectoryChangesW para monitorizar cambios en carpetas y ficheros. Cuando detecta alguno de los siguientes cambios, se reinicia el application domain (esta lista la he sacado directamente del siguiente post de mi compañera Tess):

     

    ·         Modificaciones en Web.Config, Machine.Config o Glabal.asax

    ·         Modificaciones en la carpeta ~/bin o en su contenido

    ·         La ruta física del directorio virtual es modificada

    ·         Alguna subcarpeta de la aplicación web es eliminada (sólo en ASP.NET 2.0)

     

    Adicionalmente, los siguientes eventos también provocan un reinicio del application domain:

     

    ·         La política CAS (Code Access Security) es modificada.

    ·         El número de recompilaciones dinámicas1 de ficheros *.aspx, *.ascx o *.asax excede el límite especificado en la propiedad <compilation numRecompilesBeforeAppRestart="15"/> en el machine.config o web.config  (por defecto este valor es de 15)

     

    1) Es importante puntualizar que modificar un fichero *.aspx, *.ascx o *.asax también implica una recompilación, dado que en ASP.NET absolutamente todo el código termina siendo compilado, incluso el código “HTML” de la capa de presentación. Las recompilaciones dinámicas, se gestionan de una forma distinta ya que en lugar de reiniciar el application domain directamente, se invalida el ensamblado con la versión antigua del fichero .aspx, y se carga un nuevo ensamblado con la nueva versión.

     

    A la larga, dado que no se pueden descargar ensamblados de un application domain, este comportamiento de ASP.NET supondría que el consumo de memoria aumentaría progresivamente debido a la acumulación de ensamblados invalidados. Por este motivo, ASP.NET reinicia el application domain llegado un número determinado de recompilaciones, para de esta forma poder descargar los ensamblados invalidados.

     

    Por lo tanto, para evitar los problemas asociados a los reinicios de los application domains y a las recompilaciones dinámicas, debemos seguir las siguientes medidas:

    a)      Realizar despliegues a producción cuando el servidor tenga la menor carga posible, o mejor incluso sacándolo del balanceo de forma que no atienda peticiones.

    b)      Cuando el punto a) no sea posible, podemos añadir / modificar los parámetros waitChangeNotification y maxWaitChangeNotification para minimizar el número de reinicios del application domain. Estos parámetros se pueden configurar en el web.config de la aplicación web en cuestión. Por ejemplo:

    <system.web>

      <httpRuntime waitChangeNotification="10" maxWaitChangeNotification="60" />

    </system.web>

     

    Este mecanismo funciona de la siguiente manera: en el momento que sobrescribimos un fichero existente dentro de la estructura de carpetas de nuestra aplicación web, un objeto de File Change Notification detecta el cambio y transcurrido el tiempo que hayamos configurado en el campo waitChangeNotification reiniciará el application domain. Si durante este periodo de espera detecta otro cambio adicional, volverá a esperar otra vez el número de segundos que hayamos configurado en waitChangeNotification, y así sucesivamente hasta que se alcance el tiempo de espera que hayamos configurado en maxWaitChangeNotification. Cuando se alcanza el tiempo de espera configurado en maxWaitChangeNotification, el application domain se reinicia al margen de que se sigan detectando cambios.

    De esta forma, el application domain no se reinicia hasta que se hayan “terminado de copiar” todos los nuevos ficheros a la carpeta de contenido, evitando así múltiples reinicios con su consiguiente coste. Esta problemática también está descrita en detalle en el post al que hacía referencia antes de mi compañera Tess Ferrandez.

     

    c)      En ocasiones es interesante probar si deshabilitando la “compilación por lotes” mejora el comportamiento. Este cambio ocasiona que no se compile la aplicación entera cuando se carga el application domain sino únicamente las partes relevantes de la aplicación requeridas para atender la primera petición. Por un lado se reduce el retraso inicial para responder a la primera petición, aunque por otro lado, cuando llegue una petición posterior que requiera una parte de la aplicación que no haya sido compilada, esa petición también experimentará un retraso.

    Este cambio aplica sobre todo para aplicaciones compuestas por gran cantidad de ensamblados y páginas.

    <system.web>

      <compilation batch="false" />

    </system.web>

     

    Espero que esta información os sea de utilidad.

    - Daniel Mossberg

  • The code is out there

    Cómo capturar volcados de hang con Debug Diagnostics Tool

    Posted over 4 years ago
    by Daniel Mossberg
    • 0 Comments

    Este post es la continuación de Cuándo y cómo capturar volcados de memoria en modo Hang

    La herramienta Debug Diagnostics Tool se puede descargar desde aquí, tanto la versión de 32-bit como la de 64-bit. En el momento de escribir este post, la herramienta está en la versión 1.1 y está soportada para los siguientes sistemas operativos:

     

    ·         Windows 2000 Server

    ·         Windows Server 2003

    ·         Windows XP

     

    Es decir, si pretendemos analizar un problema en Windows Server 2008, Windows Server 2008 R2, Windows Vista o Windows 7 es recomendable utilizar otras herramientas, aunque algunas funcionalidades de Debug Diagnostics pueden funcionar en estos sistemas operativos.

     

    Nota

    En el caso de los sistemas operativos de 64-bit, es conveniente instalar la versión de DebugDiag en función del proceso que se va a monitorizar. Es decir, si el proceso es de 32-bit, es mejor utilizar la versión de 32-bit de DebugDiag al margen de que Windows sea de 64-bit (de esta forma nos quitamos del medio la capa WOW64 que a veces dificulta la depuración).

     

    DebugDiag nos permite capturar volcados de hang básicamente de tres formas distintas:

     

    Volcado manual de todos los procesos de IIS

    Volcado manual de un proceso específico

    Regla de hang

     

     

     

    VOLCADO MANUAL DE TODOS LOS PROCESOS DE IIS

    Podemos generar los volcados manualmente cuando se esté reproduciendo el problema desde el menú [Tools] à [Create IIS/COM+ Hang Dump].

      image

    Por defecto, los volcados se generarán en la ruta: C:\Program Files\DebugDiag\Logs\Misc

     

    VOLCADO MANUAL DE UN PROCESOS ESPECÍFICO

    Podemos generar volcados manualmente de un proceso específico desde la pestaña [Processes] à [Botón derecho sobre el proceso] à [Create Full Userdump]

     

     image

     

    Al igual que en el anterior caso, los volcados se generan en la ruta: C:\Program Files\DebugDiag\Logs\Misc.

     

    REGLA DE HANG

    Las reglas de hang de Debug Diagnostic permiten monitorizar la salud de una aplicación web de forma automatizada, y generar volcados en el momento que la herramienta detecta que la aplicación no respohnde en los tiempos establecidos.

     

    Para configurar una regla de hang debemos posicionarnos en la pestaña [Rules] y posteriormente pulsar sobre el botón  [Add Rule…]. Posteriormente seguiremos estos pasos:

     

    1)      Seleccionar la opción [IIS Hang]

    2)      En la siguiente pantalla, seleccionar las URLs que queremos monitorizar con la herramienta. Debemos configurar al menos una URL, pero en algunos escenario puede tener sentido monitorizar varias. El funcionamiento del mecanismo de monitorización es muy sencillo. Se basa en realizar peticiones HTTP a las URLs configuradas, y esperar hasta que una de esas peticiones no sea atendida en el tiempo establecido para alertar al depurador que se está produciendo un hang.

    Para cada URL, configuraremos independientemente la propiedad ping interval, que es el intervalo de tiempo entre cada ping a la URL, y la propiedad timeout que es el intervalo de tiempo que vamos a esperar a recibir una respuesta. En el momento que la respuesta al ping exceda el tiempo que hemos especificado en timeout, se generará un volcado automáticamente.

    Los valores predeterminados de la herramienta establecen el valor de la propiedad timeout a 120 segundos. Personalmente opino que es un valor muy excesivo, y que en la mayoría de los casos, si una petición HTTP no ha respondido en 30 segundos, se puede confirmar que se está produciendo una contención. Es difícil hacer una recomendación genérica sobre estos parámetros, porque depende mucho de cada aplicación y cada escenario individual. Yo suelo recomendar poner ambos valores a 30, pero al final eso lo tiene que decidir quien conoce la aplicación.

    image

    3)      En el siguiente paso, debemos configurar que hará la herramienta cuando detecte que se está produciendo una situación de hang. Para ello debemos pulsar sobre el botón [Add Dump Target] y seleccionar alguna de las siguiente opciones:

     

    a)      All active IIS/COM+ related processes: Generará volcados de todos los procesos INETINFO.EXE, W3WP.EXE y DLLHOST.EXE. Esta opción es interesante cuando no sabemos a priori cual es el proceso que provoca la contención.

    b)      Executable: Generará volcados de todos los procesos con el mismo nombre que el proceso que indiquemos, por ejemplo W3WP.EXE.

    c)      COM+ application: Generará un volcado del proceso DLLHOST.EXE que aloje la aplicación COM+ que indiquemos.

    d)      Web application pool: Generará un volcado de memoria del proceso W3WP.EXE que aloja el application pool que indiquemos.

    e)      NT Service: Generará un volcado del proceso que aloje al servicio de Windows que indiquemos, por ejemplo SVCHOST.EXE.

    4)      Por último debemos indicar el nombre de la regla y la ubicación donde volcará los datos generados. En función de la circunstancias, estos datos pueden ser de gran tamaño por lo que conviene elegir una unidad que no tenga problemas de espacio.

     

    - Daniel Mossberg

  • The code is out there

    Cuándo y cómo capturar volcados de memoria en modo Hang

    Posted over 4 years ago
    by Daniel Mossberg
    • 0 Comments

    En el contexto de este post, se entiende por hang (yo lo traduciría al castellano como contención) una situación en la que una aplicación responde mucho más despacio de lo normal o deja de responder por completo. Una contención puede ser temporal (pasado un rato la aplicación vuelve a funcionar correctamente) o permanente (lo que se conoce como deadlock), y puede ir asociada a un consumo de CPU elevado o un consumo bajo (o incluso nulo).

     

    Los volcados de memoria en modo hang muestran el estado de la memoria de un proceso en un momento determinado en el tiempo. Por lo tanto, para que la información contenida en el volcado tenga algún valor es imprescindible capturar el volcado en el momento adecuado. Revisemos los distintos escenarios en los que necesitaremos capturar volcados de memoria en modo hang:

     

    Contención y consumo de CPU bajo

    Habitualmente, cuando la aplicación no responde ni consume CPU es que está esperando a algo que se ejecuta en un proceso distinto o incluso en un servidor distinto. Por ejemplo, puede estar esperando a obtener respuesta de una llamada RPC, a obtener respuesta de una llamada por sockets, o a que se libere una sección crítica. En ocasiones, como ya adelantaba antes, estas contenciones pueden ser permanentes si esa respuesta nunca llega o la sección crítica nunca se libera (siguiendo con el ejemplo anterior).

     

    Para este tipo de problemas es interesante capturar tres volcados con un intervalo de un minuto entre cada uno (aproximadamente), y cuando sea posible capturar volcados de todos los procesos implicados en la aplicación cuando se esté reproduciendo el problema.

     

    Esto nos permitirá ver cómo evoluciona el proceso con el tiempo (por eso cogemos varios volcados), y además obtendremos datos de todos los procesos implicados dado que a priori no podemos saber en qué proceso se produce la contención.

     

    Las dos herramientas más comunes para capturar estos volcados son Debug Diagnostics y Adplus. En los siguientes posts encontraréis la información detallada sobre cómo generar los volcados:

     

    ·         Cómo capturar volcados de hang con Debug Diagnostics Tool

    ·         Cómo capturar volcados de hang con Adplus (próximamente)

     

    Contención y consumo de CPU elevado

    Para este tipo de problemas, al contrario que la categoría anterior, es más evidente cual es el proceso que nos interesa analizar (el que consume CPU). Por lo tanto, la forma de capturar los volcados es ligeramente distinta.

     

    Para este tipo de problemas es interesante capturar tres volcados con un intervalo de un minuto entre cada uno (aproximadamente) del proceso específico con elevado consumo de CPU cuando se esté reproduciendo el problema.

     

    Al igual que en el anterior escenario, podemos utilizar las herramientas Debug Diagnostics y Adplus para capturar este tipo de volcados. Adicionalmente, existe una herramienta llamada ProcDump diseñada especialmente para generar volcados cuando un proceso supere un determinado umbral de consumo de CPU. En los siguientes posts encontraréis la información detallada sobre cómo generar los volcados:

     

    ·         Cómo capturar volcados de hang con Debug Diagnostics Tool

    ·         Cómo capturar volcados de hang con Adplus (próximamente)

    ·         Cómo capturar volcados de hang con ProcDump (próximamente)

     

    Próximamente veremos cuándo y cómo capturar volcados de memoria en modo crash.

     

    Hasta pronto

    - Daniel Mossberg

  • The code is out there

    Detalles sobre el error HTTP 413 - Request entity too large

    Posted over 4 years ago
    by Daniel Mossberg
    • 3 Comments

    Imaginemos el siguiente escenario: Tenéis una aplicación web que requiere o acepta certificados de cliente, y que mediante un formulario HTML hace POST para subir ficheros al servidor. En algunas ocasiones, cuando los ficheros superan un determinado tamaño, la petición falla y en los logs de IIS vemos un error HTTP 413 – Request entity too large.

     

    Este es un comportamiento conocido cuando están habilitados certificados de cliente en combinación con que se realiza un petición HTTP de gran tamaño (por ejemplo un POST HTTP adjuntando ficheros). El motivo es que IIS lee los primeros n bytes de la petición (ahora veremos qué determina esa n), asumiendo que deberían haber llegado todos los encabezados de la petición (HTTP headers), pero no necesariamente todos los datos asociados (entity body) de la petición.

     

    Esto habitualmente no supone un problema, pero en nuestro escenario en particular, IIS examina los encabezados de la petición, encuentra que la página requiere certificados de cliente y que por lo tanto necesita renegociar la conexión SSL. Desafortunadamente, el cliente (IE) no puede renegociar la conexión porqué está esperando a poder enviar el resto de datos del entity body a IIS. Para evitar que se produzca un deadlock (IIS esperando a poder renegociar la conexión SSL e IE esperando a poder mandar el entity body), IIS devuelve un error HTTP 413 y cierra la conexión.

     

    Para evitar que se produzca este problema tenemos dos opciones:

     

    1)      Para que la renegociación de la conexión se pueda llevar a cabo, el entity body completo tiene que estar “precargado” utilizando SSL Preload. SSL Preload utiliza la propiedad de la metabase UploadReadAheadSize para determinar el tamaño del buffer en el que almacenará la petición (los n bytes a los que hacía referencia antes). Por lo tanto si el tamaño de este buffer es inferior al tamaño total de la petición HTTP (lo que incluye los encabezados HTTP y el entity body), IIS devuelve el error HTTP 413.

    El tamaño predeterminado de ese buffer son 48k, lo cual es un tamaño insuficiente en muchas ocasiones, pero que fue establecido así para evitar ataques de denegación de servicio subiendo ficheros “basura” de gran tamaño.

    La sintaxis para aumentar dicho buffer es la siguiente, y se puede especificar de forma granular a nivel de directorio virtual:

    C:\inetpub\adminscripts>cscript adsutil.vbs set w3svc/1/root/myApp/uploadreadaheadsize 20000000

    Microsoft (R) Windows Script Host Version 5.6

    Copyright (C) Microsoft Corporation 1996-2001. All rights reserved.

    uploadreadaheadsize             : (INTEGER) 20000000


    El número indicado es el número de bytes, por lo que 20.000.000 bytes (como en el ejemplo) serían aproximadamente unas 19,1MB.

     

    2)      La otra alternativa es habilitar la clave de la metabase SSLAlwaysNegoClientCert de forma que forcemos a que por cada nueva conexión SSL que se establezca, el servidor negociará inmediatamente el certificado de cliente. De esta forman se previene la renegociación, lo cual supone una ganancia de rendimiento en sí misma, y además evita el problema asociado a las peticiones con entity body de gran tamaño. La sintaxis para habilitar esta clave es:

    C:\inetpub\adminscripts>cscript adsutil.vbs set w3svc/1/SSLAlwaysNegoClientCert true

    Microsoft (R) Windows Script Host Version 5.6

    Copyright (C) Microsoft Corporation 1996-2001. All rights reserved.

     

    SSLAlwaysNegoClientCert         : (BOOLEAN) True

     

    INFORMACIÓN ADICIONAL

    Client cannot renegotiate request and returns an HTTP 413 error (IIS 6.0)

    http://www.microsoft.com/technet/prodtechnol/WindowsServer2003/Library/IIS/7e0d74d3-ca01-4d36-8ac7-6b2ca03fd383.mspx?mfr=true

                           

     Happy hacking

    - Daniel Mossberg

  • The code is out there

    Acentos y eñes no se muestran correctamente

    Posted over 4 years ago
    by Daniel Mossberg
    • 3 Comments

    Los ordenadores procesan texto utilizando tablas de codificación para convertir secuencias de bits en caracteres alfanuméricos y viceversa. Cuando desarrollamos aplicaciones, y muy especialmente aplicaciones distribuidas o aplicaciones web, tenemos que tener en cuenta la codificación (o enconding) con la que “ciframos” y “desciframos” los mensajes, dado que si utilizamos codificaciones distintas el mensaje resultante puede resultar incorrecto.

     

    Antes de meternos de lleno en las consideraciones específicas de desarrollo, revisemos brevemente los encodings más comunes que puede ser interesante conocer.

     

    ASCII

    ASCII (American Standard Code for Information Interchange) se desarrolló en los años 60 y es una tabla de codificación que utiliza 7 bits para representar 128 caracteres, de los cuales 94 son caracteres legibles y 33 son caracteres de control y el espacio que se considera un carácter invisible. Esta codificación no sirve para representar la ‘ñ’ o los acentos, y hoy en día en muchos contextos se considera obsoleta.

     

    Windows-1252 (Western European)

    Históricamente era la codificación predeterminada en muchos contextos de Windows. Es una codificación de 8 bits para representar 256 caracteres, e incluye la mayoría de los caracteres utilizados en los alfabetos de Europa occidental.

     

    ISO-8859-1 (Latin 1)

    Esta codificación ISO de 8 bits fue publicada en 1985, y es un subconjunto de la codificación Windows-1252 compuesto por 191 caracteres. Es informalmente conocida como Latin 1.

     

    ISO-8859-15 (Latin 9)

    En 1999 se publicó esta codificación como una actualización de ISO-8859-1 para corregir algunas de sus limitaciones. Sigue utilizando 8 bits para representar cada carácter, y entre otros cambios se añadieron algunos caracteres del alfabeto fines (Š y Ž), y el símbolo del euro (€). Para poder acomodar estos cambios, se tuvieron que eliminar algunos otros caracteres de uso poco frecuente, entre los cuales se encontraban: ¤, ¦, ¨, ´, ¼, ½, y ¾.

     

    UTF-8

    UTF-8 (8-bit Unicode Transformation Format) es una codificación de longitud variable entre 1 y 4 bytes. La representación de un byte (8 bits) de UTF-8 se reserva exclusivamente a los 128 caracteres de la tabla ASCII lo cual lo hace compatible hacia atrás. Los siguientes 1920 caracteres de UTF-8 requieren dos bytes para ser codificados, entre los cuales se incluyen el alfabeto latino, griego, cirílico, hebreo, árabe, sirio, etc.

     

    Por lo tanto, la moraleja de este breve resumen, es que aunque tanto Windows-1252 y UTF-8 sean capaces de representar el carácter ‘Ñ’, lo hacen de forma distinta. De hecho, para ser más precisos lo hacen así:  

    Codificación

    Glifo

    Representación Binaria

    Representación Hexadecimal

    Windows-1252

    Ñ

    1101001

    D1

    UTF-8

    Ñ

    1100011 10010001

    C3 91

    Por lo tanto, queda claro que si codificamos un texto utilizando Windows-1252 y posteriormente lo decodificamos los bytes resultantes utilizando UTF-8, lo que inicialmente era un ‘Ñ’ va a pasar a ser cualquier otra cosa excepto una ‘Ñ’ al decodificarlo.

     

    Ahora que ya tenemos más clara la problemática, os detallo algunas consideraciones concretas para ASP.NET:

     

    1)      Dado que ASP.NET nos permite configurar la codificación utilizada para generar las respuestas, y la codificación predeterminada para decodificar las peticiones, es importante asegurarse de que se utiliza la misma codificación en la otra parte implicada, el cliente. Esta es la configuración predeterminada de ASP.NET:

    <configuration>
      <
    system.web>
        <
    globalization

          requestEncoding="utf-8"

          responseEncoding="utf-8" />
      </
    system.web>
    </
    configuration>

     

    2)      En una respuesta HTTP podemos especificar la codificación del contenido en dos sitios. En el encabezado HTTP Content-Type o en un meta tag dentro del propio documento HTML. En caso de conflicto entre ambos (como en el ejemplo), tiene preferencia la configuración especificada en el encabezado HTTP.

     

    HTTP/1.1 200 OK

    Date: Tue, 1 Dec 2009 10:23:34 GMT

    Server: Microsoft-IIS/6.0

    Content-Length: 48

    Content-Type: text/html; charset=utf-8

    Cache-control: private


    <
    html xmlns="http://www.w3.org/1999/xhtml">

      <head>
        <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
      </
    head>
      <
    body>
        …
      </body>

    </html>

     

    Un problema frecuente es enviar respuestas HTTP sin especificar en ningún sitio la codificación utilizada. Cuando se da el caso, el cliente que recibe la respuesta tiene que determinar la codificación que utilizará para decodificar el contenido. Habitualmente, es tan sencillo como que el cliente utilizará la codificación predeterminada del sistema operativo, por lo que existe un elevado riesgo de que en algún cliente con una determinada configuración se muestre incorrectamente.

    3)      Otro problema con el que me he encontrado en varias ocasiones es relativo al dialogo de descarga de Internet Explorer. Cuando generamos una respuesta HTTP dinamicamente desde ASP.NET para enviar un fichero, podemos encontrarnos con que el dialogo de descarga muestra el nombre del fichero con caracteres incorrectos:

    image

    Este problema se debe a que ASP.NET (1.1 y 2.0) por defecto codifican los encabezados HTTP con UTF-8. En ASP.NET 1.1 es necesario instalar la siguiente actualización para poder especificar la codificación de los encabezados:

    FIX: The response header will always be encoded as UTF-8 when you use the Response.Addheader method in ASP.NET

    http://support.microsoft.com/default.aspx?scid=kb;EN-US;895262

     

    Una vez instalada la actualización, podemos especificarla en el web.config de la aplicación:

    <globalization responseHeaderEncoding="Windows-1252"/>

     

    En ASP.NET 2.0 se puede cambiar la codificación de los encabezados HTTP programáticamente además de mediante la opción del web.config (igual que en ASP.NET 1.1):

     

    protected void Page_Load(object sender, EventArgs e)

    {

        //Leemos el fichero DOCX y lo almacenamos en un array de bytes

        string FileName = "Diseño gráfico.docx";

        string FilePath = MapPath(FileName);

        byte[] FileBytes = File.ReadAllBytes(FilePath);       

       

        //ASP.NET codifica los encabezados HTTP con 'utf-8' de forma

        //predeterminada. Especificamos que utilice la codificación 

        //ANSI predeterminada del OS, que en mi caso es 'Windows-1252'

        Response.HeaderEncoding = System.Text.Encoding.Default;   

     

        //Establecemos el MIME type de la respuesta y añadimos el

        //encabezado HTTP 'Content-Disposition'           

        Response.ContentType = "application/docx; charset=utf-8";

        Response.AddHeader("Content-Disposition", "Inline; filename=" +

            FileName);

          

        //Envíamos el fichero en formato binario

        Response.BinaryWrite(FileBytes);           

    }

     

    Hasta el próximo post

    - Daniel Mossberg

  • The code is out there

    Como solucionar problemas de conexión SSL/TLS contra IIS

    Posted over 4 years ago
    by Daniel Mossberg
    • 0 Comments

    La mayoría de los casos de soporte que nos abren para diagnosticar problemas al conectarse por HTTPS a un sitio web, suelen resolverse realizando alguno de los pasos descritos a continuación. Partimos de la premisa de que podemos acceder correctamente por HTTP para acotar el escenario a un problema al establecer la conexión HTTPS.

    1)      Lo primero que suelo comprobar es que el certificado es válido y está emitido con el propósito adecuado.

    a)      Para ello debemos examinar las propiedades del certificado de servidor, y en la pestaña General verificar que el certificado es válido y tiene una clave privada.

    clip_image002

    b)      Comprobar también en la pestaña Details que la propiedad Enhanced Key Usage (o Uso Mejorado de Clave) incluye el valor Server Authentication (1.3.6.1.5.5.7.3.1).

    clip_image003

    c)      Por último vayamos a la pestaña Certification Path y verifiquemos que todos los certificados en la cadena de certificación tienen el estado OK.

    clip_image005

    Las condiciones descritas en los pasos a, b y c constituyen el mínimo imprescindible para que se pueda establecer una conexión SSL, pero el hecho de que todo esté correcto no descarta por completo que el problema se encuentre en el certificado. Más adelante realizaremos pruebas adicionales para descartar problemas de integridad con el certificado. 

    2)      Comprobar si existe un conflicto con alguna otra aplicación que esté escuchando por el puerto 443 (o el que aplique en cada caso). Si este es el caso, es posible que veamos el siguiente evento registrado en los logs de eventos NT:

    Event Type:          Error

    Event Source:        W3SVC

    Event Category:      None

    Event ID:            1114

    Description:         One of the IP/Port combinations for site '1' has already been configured to be used by another program. The other program's SSL configuration will be used.


    Para diagnosticar esta situación, inicialmente ejecutaremos el siguiente comando:

     

    C:\WINDOWS\system32>netstat -noa

     

    Si todo es correcto, sólo debería haber una aplicación escuchando por el puerto 443, y el resultado debería ser algo parecido a esto (el PID 4 siempre equivale al proceso System):

     

    Active Connections

     

      Proto  Local Address   Foreign Address  State       PID

      TCP    0.0.0.0:80      0.0.0.0:0        LISTENING   4

      TCP    0.0.0.0:135     0.0.0.0:0        LISTENING   688

      TCP    0.0.0.0:443     0.0.0.0:0        LISTENING   4

      TCP    0.0.0.0:445     0.0.0.0:0        LISTENING   4


    Si nos surgen dudas sobre si realmente es IIS (o el proceso System) el que está escuchando por el puerto 443, podéis utilizar la herramienta tasklist.exe o simplemente Task Manager para confirmarlo. Si el puerto estuviera en uso por otra aplicación, habría que determinar qué aplicación usa el puerto y para qué. La forma de verificar si este es realmente el problema sería probar a configurar SSL en un puerto distinto y verificar si funciona.

    Si netstat no arroja luz sobre el conflicto de puertos, pero no obstante vemos el evento mencionado anteriormente (Event ID 1114) en los logs de eventos, ejecutaremos el siguiente comando para sacar un listado de los certificados SSL asociados a IIS:

    C:\Program Files\Support Tools>httpcfg query ssl

     

    Cuando revisemos la lista debemos buscar alguna entrada en la que el campo de Hash y CertStoreName están vacíos o nulos, y el Guid está todo a ceros. Este es indicativo de un problema.

    IP                      : 0.0.0.0:443

    Hash                    :

    Guid                    : {00000000-0000-0000-0000-000000000000}

    CertStoreName           : (null)

    CertCheckMode           : 0

    RevocationFreshnessTime : 0

    UrlRetrievalTimeout     : 0

    SslCtlIdentifier        : (null)

    SslCtlStoreName         : (null)

    Flags                   : 0

     

    Para solventar este problema, debemos eliminar el binding entre IIS y el certificado ejecutando el siguiente comando (la sintaxis genérica es: httpcfg delete ssl -i ip:puerto):

    C:\Program Files\Support Tools>httpcfg delete ssl -i 0.0.0.0:443

     

    Y posteriormente volver a configurar el certificado SSL desde INETMGR.EXE. Si volvemos a listar los certificados SSL con httpcfg.exe el resultado ahora debería ser algo parecido a esto:

     

    IP                      : 0.0.0.0:443

    Hash                    : d753a06831d416db4bf2 9f5 aa33ae714dfba5d

    Guid                    : {4dc3e181-e14b-4a21-b022-59fc669b0914}

    CertStoreName           : MY

    CertCheckMode           : 0

    RevocationFreshnessTime : 0

    UrlRetrievalTimeout     : 0

    SslCtlIdentifier        :

    SslCtlStoreName         :

    Flags                   : 0

     

    3)      Crear un certificado de pruebas con SelfSSL.exe para verificar que el problema no es del certificado. Esta herramienta se puede descargar como parte del Resource Kit de IIS 6.0. La herramienta genera un certificado de pruebas automáticamente y lo instala para un sitio web determinado. La sintaxis es la siguiente:

    selfssl.exe /N:CN=[Nombre Cert.] /V:[Días Validez Cert.] /S:[ID Sitio Web] /P:[Puerto SSL]

     

    Por ejemplo, para nuestro sitio web de anteriores ejemplos ejecutaríamos el siguiente comando y posteriormente volveríamos a probar si podemos establecer la conexión HTTPS:

     

    C:\Program Files\IIS Resources\SelfSSL>selfssl.exe /N:CN=mywebsite.microsoft.com /V:365 /S:1 /P:443

    Microsoft (R) SelfSSL Version 1.0

    Copyright (C) 2003 Microsoft Corporation. All rights reserved.

     

    Do you want to replace the SSL settings for site 1 (Y/N)?Y

    The self-signed certificate was successfully assigned to site 1.

     

    4)      Por último reinstalaremos el certificado seleccionando manualmente el almacén de certificados en el que se instala. Es decir, al instalar el certificado, no seleccionar la opción de Automatically select the certificate store based on the type of certificate, sino hacerlo manualmente marcando la casilla Show physical stores y seleccionando el almacén deseado desde aquí.

    clip_image006

    De esta forma descartaremos que tenemos el problema descrito en el post Certificate has private key but we get "the keyset does not exist" error.

    Si llegados a este punto seguís sin poder establecer una conexión HTTPS contra vuestro servidor, será un buen momento para abrir un caso de soporte con Microsoft.

     

    Espero que os haya sido de utilidad.

    - Daniel Mossberg

  • The code is out there

    Depuración de aplicaciones .NET con WinDbg

    Posted over 4 years ago
    by Daniel Mossberg
    • 0 Comments

    Mi compañero Alejandro Campos ha terminado recientemente una serie de posts dedicados a la depuración de aplicaciones .NET utilizando WinDbg. La serie es una referencia muy completa que explica desde cómo poner un punto de parada al ejecutar un determinado método, cómo ver que objetos consumen más memoria, cómo desensamblar un ensamblado .NET, hasta cómo ver que threads consumen más tiempo de CPU.

    http://blogs.msdn.com/alejacma/archive/2009/07/07/managed-debugging-with-windbg-introduction-and-index.aspx

  • The code is out there

    Errores HTTP 413 en conexiones SSL cuando se suben grandes ficheros

    Posted over 4 years ago
    by Daniel Mossberg
    • 0 Comments

    DESCRIPCIÓN DEL PROBLEMA

    Tenéis una aplicación ASP.NET que requiere certificados de cliente y en la que realizan peticiones HTTP de gran tamaño (por ejemplo un POST HTTP adjuntando ficheros). Cuando el tamaño de la petición (o el fichero adjunto) supera un determinado límite falla, y en los logs de IIS vemos el error HTTP 413 – Request entity too large. Si la aplicación cliente es .NET, la petición HTTP o llamada a web service fallará, y veremos el siguiente mensaje de error asociado:

     

    The underlying connection was closed: An unexpected error occurred on a send.

     

    O su variante en castellano:

     

    Se ha terminado la conexión: Error inesperado de envío

     

    RESOLUCIÓN

    Ver el post Detalles sobre el error HTTP 413.

     

    Happy hacking

    - Daniel Mossberg

  • The code is out there

    Errores 401 en IIS al habilitar la autenticación de Windows integrada

    Posted over 4 years ago
    by Daniel Mossberg
    • 0 Comments

    Hoy quería escribir sobre caso en el que trabajé hace unas semanas, no por la complejidad del problema, si no por mostrar un ejemplo de cómo abordar un problema y qué herramientas utilizar cuando no tienes ni idea de por dónde van los tiros. Los datos iniciales que tenía del problema era que el cliente tenía un portal de SharePoint en el que todas las páginas le daban errores HTTP 401 si habilitaba la autenticación de Windows integrada. Si configuraba autenticación básica o acceso anónimo los portales funcionaban correctamente.

     

    Mi primera hipótesis fue que probablemente los errores se debían a un problema en la configuración de Kerberos, pero pronto descubrí que tenían configurada únicamente la autenticación por NTLM. Kerberos estaba deshabilitado en la configuración de IIS:

     

    C:\Inetpub\AdminScripts>cscript adsutil.vbs get W3SVC/1/NTAuthenticationProviders

    Microsoft (R) Windows Script Host Version 5.6

    Copyright (C) Microsoft Corporation 1996-2001. All rights reserved.

     

    NTAuthenticationProviders       : (STRING) "NTLM"

     

    Puesto que no tenía ni idea de cuál podría ser la causa de este comportamiento, le pedí los siguientes logs al cliente:

     

    ·         La metabase de IIS (en Windows Server 2003 se encuentra en la siguiente ruta: %WINDIR%\system32\inetsrv\metabase.xml).

    ·         Los logs de IIS.

    ·         Unas trazas de red capturadas mientras reproducían el error.

     

    Examinando los logs de IIS veíamos los errores HTTP 401, pero no arrojaban luz sobre el origen del problema:

     

    #Fields: date time s-sitename … sc-status sc-substatus sc-win32-status

    2009-09-16 14:37:46 W3SVC1 … 401 2 2148074254

    2009-09-16 14:37:46 W3SVC1 … 401 1 0

     

    Yo esperaba encontrar alguna pista al menos a partir del estado win32, pero el estado 2148074254 (No credentials are available in the security package) en la primera petición es esperado (ver el siguiente post para más detalles), y en la segunda petición el resultado era 0 (The operation completed successfully). Y lo más extraño de todo, es que no se registraba una tercera y última petición para finalizar la secuencia de autenticación como sería lo normal (de nuevo, ver el post al que hacía referencia antes).

     

    Analizando las trazas de red veíamos la secuencia de peticiones habitual durante el handshake NTLM, pero cuando el cliente hace la petición final autenticada (en el frame TCP 5004), en el siguiente frame TCP el cliente cierra la conexión TCP deliberadamente ([FIN, ACK]) y por tanto la respuesta del IIS nunca llega.

     

    No.  Time            Source   Dest.    Prot. Info

    ---- --------------- -------- -------- ----- --------------------------------------------------

    4503 16:09:05.973298 [CLIENT] [SERVER] TCP   timeflies > 8010 [SYN] Seq=0 Win=65535 Len=0 MSS…

    4504 16:09:05.973298 [SERVER] [CLIENT] TCP   8010 > timeflies [SYN, ACK] Seq=0 Ack=1 Win=1638…

    4505 16:09:05.973298 [CLIENT] [SERVER] TCP   timeflies > 8010 [ACK] Seq=1 Ack=1 Win=65535 Len…

    4506 16:09:05.973298 [CLIENT] [SERVER] HTTP  GET / HTTP/1.1

    4507 16:09:05.983312 [SERVER] [CLIENT] TCP   [TCP segment of a reassembled PDU]

    4508 16:09:05.983312 [SERVER] [CLIENT] HTTP  HTTP/1.1 401 Unauthorized  (text/html)

    4509 16:09:05.983312 [CLIENT] [SERVER] TCP   timeflies > 8010 [ACK] Seq=441 Ack=1910 Win=6553…

    4995 16:09:18.230923 [CLIENT] [SERVER] TCP   timeflies > 8010 [FIN, ACK] Seq=441 Ack=1910 Win…

    4996 16:09:18.230923 [CLIENT] [SERVER] TCP   ndm-requester > 8010 [SYN] Seq=0 Win=65535 Len=0…

    4997 16:09:18.230923 [SERVER] [CLIENT] TCP   8010 > timeflies [ACK] Seq=1910 Ack=442 Win=6509…

    4998 16:09:18.230923 [SERVER] [CLIENT] TCP   8010 > ndm-requester [SYN, ACK] Seq=0 Ack=1 Win=…

    4999 16:09:18.230923 [CLIENT] [SERVER] TCP   ndm-requester > 8010 [ACK] Seq=1 Ack=1 Win=65535…

    5000 16:09:18.230923 [CLIENT] [SERVER] HTTP  GET / HTTP/1.1 , NTLMSSP_NEGOTIATE

    5001 16:09:18.240938 [SERVER] [CLIENT] TCP   [TCP segment of a reassembled PDU]

    5002 16:09:18.240938 [SERVER] [CLIENT] HTTP  HTTP/1.1 401 Unauthorized , NTLMSSP_CHALLENGE (t…

    5003 16:09:18.240938 [CLIENT] [SERVER] TCP   ndm-requester > 8010 [ACK] Seq=519 Ack=2082 Win=…

    5004 16:09:18.240938 [CLIENT] [SERVER] HTTP  GET / HTTP/1.1 , NTLMSSP_AUTH, User: EMEA\daniem

    5005 16:09:18.240938 [CLIENT] [SERVER] TCP   ndm-requester > 8010 [FIN, ACK] Seq=1221 Ack=208…

    5006 16:09:18.240938 [SERVER] [CLIENT] TCP   8010 > ndm-requester [ACK] Seq=2082 Ack=1222 Win…

     

    Las trazas de red explicaban el comportamiento, pero seguía sin saber porqué el cliente finalizaba la conexión TCP. Examinando más detenidamente el tráfico HTTP, descubrí que en las respuestas HTTP que devolvía IIS, se estaba incluyendo consistentemente el encabezado "Connection: close”:

     

    HTTP/1.1 401 Unauthorized\r\n

    Content-Length: 1539\r\n

    Content-Type: text/html\r\n

    Server: Microsoft-IIS/6.0\r\n

    [truncated] WWW-Authenticate: NTLM TlRMTVNTUAACAAAACAAIADgAAA…

    X-Powered-By: ASP.NET\r\n

    MicrosoftSharePointTeamServices: 12.0.0.6007\r\n

    Date: Wed, 24 Jun 2009 16:09:18 GMT\r\n

    Connection: close\r\n

     

    Revisando de nuevo la configuración de IIS (en el fichero metabase.xml), pude confirmar que los Keep-Alives estaban deshabilitados para todo el IIS:

     

    <IIsWebService Location="/LM/W3SVC" AllowKeepAlive="FALSE" ... />

     

    Habilitándolo de nuevo el problema quedaba resuelto y la autenticación NTLM comenzó a funcionar correctamente.

    clip_image002

    Nota: Esta dependencia de los HTTP Keep-Alives es específica de NTLM y no de la autenticación de Windows integrada en general. La autenticación Kerberos puede funcionar al margen de que se habiliten los Keep-Alives o no.

     

    Para más información, leer el siguiente artículo de TechNet:

     

    401.1 and 401.2-Authentication Problems (IIS 6.0)

    http://www.microsoft.com/technet/prodtechnol/WindowsServer2003/Library/IIS/8feeaa51-c634-4de3-bfdc-e922d195a45e.mspx?mfr=true

      

    Espero que os haya sido de utilidad

    - Daniel Mossberg

  • The code is out there

    Cosas que deberías saber sobre los destructores en .NET

    Posted over 4 years ago
    by Daniel Mossberg
    • 0 Comments

    Este post es la continuación de Cosas que deberías saber sobre el Garbage Collector de .NET

     

    ¿Para qué necesitamos destructores en .NET? Como vimos en el anterior post, en .NET no tenemos que preocuparnos de destruir ni de liberar el espacio de los objetos dado que el GC se ocupa de esta tarea. No obstante, con mucha frecuencia nuestras aplicaciones mantienen referencias a objetos nativos (es decir, no .NET), como por ejemplo handles a ficheros, conexiones a bases de datos, etc. El GC sólo entiende de objetos .NET, y por lo tanto queda en manos del desarrollador asegurarse de que su código limpia y destruye adecuadamente los recursos nativos de los que hace uso.

     

    Para asegurarnos que nuestros objetos .NET limpian toda la memoria no manejada cuando su vida finalice, tenemos que hacer dos cosas. La primera es que nuestra clase implemente la interfaz IDisposable y por tanto el método Dispose(). Este método debe ocuparse de realizar las tareas de limpieza de recursos no manejados, y finalmente llamar al método SuppressFinalize, enseguida veremos porqué.

     

    GC.SuppressFinalize(this);

     

    Para cierto tipo de objetos, el método Dispose() se le llama con otro nombre por motivos puramente semánticos. Por ejemplo, la clase SqlConnection tiene un método Close() y un método Dispose() que son funcionalmente equivalentes. En el caso de conexiones a base de datos, para la mayoría de los desarrolladores resulta más intuitivo llamara a Close() a la hora de cerrar la conexión y liberar los recursos asociados.

     

    En C# (si no me equivoco VB no tiene nada equivalente) existe la instrucción using que nos permite establecer un contexto en el que se va a utilizar un objeto .NET. Al salir de dicho contexto (cuando llegamos a la llave de cierre ‘}’) se llamará automáticamente al método Dispose() del objeto en cuestión. En el siguiente ejemplo, al salir del contexto llamaríamos al método Dispose() del objeto fs:

     

    string path = @"c:\temp\MyTest.txt";

    using (FileStream fs = File.Create(path))

    {

        byte[] info = new UTF8Encoding(true).GetBytes("This is some text");

        fs.Write(info, 0, info.Length);

    }

     

    El problema de la interfaz IDisposable es que dependemos de que los desarrolladores que consuman nuestras clases se acuerden de llamar a los métodos Dispose() o Close(), porque de lo contrario todo el esfuerzo habrá sido inútil. En previsión de que a los desarrolladores a veces se les pueda olvidar deshacerse correctamente de sus objetos, existen los destructores (en C#) o método Finalize() (en VB). En adelante me referiré a ambos cómo método Finalize(), pero son equivalentes y simplemente tienen nombres distintos por motivos lingüísticos.

     

    Por lo tanto, la segunda de las cosas que debemos hacer es precisamente implementar un método Finalize(). El método Finalize() es un mecanismo que nos proporciona .NET para ejecutar código de finalización de un objeto de forma automática, antes de que el GC libere su memoria.

     

    Uno de los principios básicos de .NET es que el código del método Finalize() de una clase debe ser muy rápido, nunca puede lanzar una excepción ni tampoco bloquear la ejecución. Los métodos Finalize() de todos los objetos de un proceso se ejecutan en un mismo thread conocido como Finalizer. Cuando el GC determina que los objetos están listos para ser finalizados los mete en una cola y dicho thread ejecuta los métodos Finalize() de cada objeto secuencialmente. Hasta que no termina la ejecución del método Finalize() de un objeto, no comienza con el siguiente. Por lo tanto si se produce un bloqueo durante la finalización de alguno de los objetos, todo lo que venga detrás nunca será finalizado con todas las consecuencias que ello conlleva. Adicionalmente, en ASP.NET, este thread es el único que no tiene un manejador de excepciones predeterminado, por lo que cualquier excepción en este thread provocaría la finalización inmediata del proceso (también conocido como crash).

     

    Para maximizar el rendimiento de nuestras aplicaciones .NET, debemos procurar que la menor cantidad posible de objetos sean finalizados por el GC. En el mejor de los casos, el método Finalize() no sería llamado nunca, sino que todos los objetos serían finalizados mediante la interfaz IDisposable. Para que os hagáis una idea de la penalización para el rendimiento que supone que un objeto sea finalizado por el GC, observad las diferencias en la tabla a continuación:

     

    Finalización mediante Dispose()

    Finalización mediante Finalize()

    Vida útil del objeto


    Al crearse un nuevo objeto .NET, el CLR comprueba si este tiene un método Finalize(). Si es así, añade una referencia a dicho objeto en una estructura manejada por el GC llamada la cola de Finalización. Esta cola contiene todos los objetos que hay que finalizar, antes de que el GC libere su memoria.

     


    Cuando finaliza la vida útil del objeto, desde el código de la aplicación se llama su método Dispose(). En este método se realizan
    las tareas de limpieza de recursos no manejados, y finalmente se llama al método SuppressFinalize lo cuál elimina el objeto de la cola de Finalización.

    Cuando finaliza la vida útil del objeto, desde el código de la aplicación no se llama su método Dispose().

    Primera colección del GC


    Cuando se desencadena una colección de basura, el GC recorre la memoria y determina qué objetos ya no están en uso y pueden ser colectados. Adicionalmente comprueba si estos objetos están referenciados en la cola de Finalización.

    Si el objeto no necesita ser finalizado, su memoria es liberada.


    Los objetos que se encuentran en la cola de Finalización son eliminados de esta y movidos a una nueva cola llamada Freachable. La cola Freachable contiene los objetos que están listos para que su método Finalize() sea llamado. Es decir, son basura pero todavía no han sido finalizados. Esta cola actúa como un objeto raíz e impide que los objetos a los que referencia sean colectados.


    El objeto sobrevive la colección de basura y promociona a la siguiente generación (a no ser que ya estuviera en la generación 2).

    Tiempo transcurrido entre colecciones

     


    El thread del Finalizer comienza a llamar a los métodos Finalize() de todos los objetos que se encuentran en la cola Freachable. Una vez ha finalizado la llamada al método Finalize(), los objetos son eliminados de esta cola. Cuando la cola Freachable se vacía, el thread del Finalizer pasa en un estado de espera.

    Segunda colección del GC

     

    Cuando se desencadena una colección de basura, el GC recorre la memoria y determina qué objetos ya no están en uso y pueden ser colectados. Adicionalmente comprueba si estos objetos están referenciados en la cola de Finalización.

     

     

    Dado que el objeto ya no se encuentra referenciado ni por la cola de Finalización ni por la cola Freachable, su memoria es liberada.

     

    En definitiva, necesitamos implementar un destructor para nuestras clases, pero debemos procurar que dicho destructor no sea utilizado nunca. Espero que os haya convencido.

     

    - Hasta el próximo post,

    Daniel Mossberg

  • The code is out there

    Cosas que deberías saber sobre el Garbage Collector de .NET

    Posted over 4 years ago
    by Daniel Mossberg
    • 0 Comments

    La mayoría de los desarrolladores de .NET tenemos algunas nociones básicas sobre que es el Garbage Collector (en adelante GC) y para qué sirve. No obstante, con frecuencia pasamos por alto algunos detalles de su funcionamiento interno que provocan que nuestro código no sea todo lo eficiente y escalable que debería.

     

    El objetivo del GC es proporcionar una capa de abstracción para los desarrolladores en cuestiones de manejo de memoria. Esto introduce una gran ventaja sobre otros lenguajes de programación en los que el desarrollador se tiene ocupar por completo de esta tarea. Escribir código que maneje correctamente su memoria en todas las situaciones no es ni mucho menos trivial, y las posibilidades de introducir bugs en la aplicación son múltiples: corrupción del heap, corrupción del stack, pérdida de memoria, fragmentación de memoria etc.

     

    Si bien el GC simplifica la tarea de manejo de memoria para los desarrolladores, no les exime por completo. Para hacer buen uso de la memoria en .NET es importante conocer como la maneja el GC internamente. Vayamos por partes.

     

    El GC de .NET es un colector de basura generacional. Esto significa que clasifica los objetos en distintas generaciones, lo cual le permite realizar colecciones de basura parciales (de una o varias generaciones) y así evitar hacer siempre colecciones de basura completas de todo el heap de .NET. Esta característica es una de las más importantes en cuanto al rendimiento del GC, y permiten que el GC de .NET sea escalable para aplicaciones de alta concurrencia como por ejemplo aplicaciones ASP.NET.

     

    En el GC de .NET tiene tres generaciones (0, 1 y 2), y todos los objetos se crean en la generación 0 siempre y cuando no superen el tamaño de 85.000 bytes (enseguida veremos qué pasa con estos objetos). Las colecciones de basura se desencadenan cuando se intenta reservar memoria para un nuevo objeto y se sobrepasa el límite de memoria designado a la generación en cuestión. Los límites de memoria asignados a cada generación se modifican dinámicamente durante la vida del proceso para adaptarse a los patrones de reserva de memoria de la aplicación.

     

    Cuando el GC realiza una colección de basura, revisa todos los objetos de la generación o generaciones afectadas, y comprueba si estan referenciados. Para que un objeto se consideré referenciado, tiene que estar referenciado por un objeto raíz. Los objetos raíz son (simplificando un poco):

     

    ·         Threads – Todos los objetos referenciados en la pila: variables locales, parámetros, etc.

    ·         Strong Reference – Objetos estáticos, objetos de caché y variables globales.

    ·         Weak Reference – Aunque los objetos WeakReference no evitan que sus objetos referenciados sean “colectados”, se consideran objetos raíz.

    ·         Pinned Objects – Los objetos marcados como Pinned no pueden ser “colectados” ni movidos por el GC, y por tanto los objetos a los que estos referencian tampoco pueden ser colectados. Esta técnica se suele utilizar para pasar un objeto .NET como referencia a una API nativa (no .NET), de forma que la dirección de memoria del objeto .NET no cambie hasta que no finalice la llamada a la API. Los objetos deben permanecer Pinned lo mínimo indispensable dado que pueden causar fragmentación del heap de .NET.

    ·         Objetos que implementan destructor o Finalize() – Esta categoría la trato en un post separado: Cosas que deberías saber sobre los destructores en .NET

     

    Los objetos que no están referenciados serán eliminados por el GC y su espacio en memoria será liberado, y los objetos supervivientes a la colección serán promocionados a la siguiente generación con la excepción de los objetos en la generación 2 que ya no pueden promocionar más. Por último, los "huecos" de espacio libre de los objetos eliminados es consolidado de forma que los objetos supervivientes son reubicados en direcciones de memoria contiguas. Una aplicación con una ratio de colecciones saludable, suele tener 10 veces más colecciones de la generación 0 que de la generación 1, y 10 veces más colecciones de la generación 1 que de la generación 2, es decir un ratio de 100:10:1 para GEN 0:GEN 1:GEN 2.

     

    El colector de basura generacional es indispensable para alcanzar el nivel de rendimiento necesario en una aplicación de alta concurrencia, y se basa en la siguiente regla heurística: los objetos que han existido mucho tiempo, van a seguir existiendo durante mucho tiempo más. Es decir que si un objeto ha sobrevivido a dos colecciones y ha promocionado hasta la generación 2, lo más probable es que vaya a seguir sobreviviendo a colecciones venideras. Por lo tanto no tiene sentido colectar basura con la misma frecuencia en la generación 2 que en la 0. Tras haber realizado miles de pruebas de carga con distintos tipos de aplicaciones, esta presunción ha resultado ser cierta (casi siempre).

     

    Cómo hacía referencia antes, los objetos cuyo tamaño es superior a los 85.000 bytes reciben un trato distinto. Estos objetos se crean en el Large Object Heap (en adelante LOH), también conocido a veces como generación 3. ¿Porqué necesitamos una generación o un heap especial para objetos grandes? Básicamente por dos motivos.

     

    1)      Se asume que los objetos grandes generalmente tienen una vida larga (misma regla heurística que para la generación 2).

    2)      Los objetos grandes son “caros” de mover y por este motivo el espacio libre en el LOH no se consolida y por tanto favorece la fragmentación de memoria. Por esto los objetos grandes se crean en un heap específico.

     

    Cuando se desencadena una colección en la generación 2 o en el LOH, se realiza una colección completa (es decir de las generaciones 0, 1, 2 y LOH). Las colecciones de basura completas son costosas, sobre todo en cuanto a consumo de CPU, dado que potencialmente hay muchos objetos que revisar y mover una vez se ha liberado el espacio.

     

    Dicho esto, ¿qué consideraciones debemos tener en cuanto al uso de memoria cuando desarrollamos aplicaciones .NET? Estas son algunas, pero no dudéis en aportar vuestros propios comentarios:

     

    ·         Cuidado con los objetos alojados en el LOH. Siempre que tenga sentido, es deseable reutilizarlos y mantenerlos referenciados durante toda la vida del proceso.

    ·         Cuidado con la concatenación de cadenas (String) en un bucle, por ejemplo generando dinámicamente un XML o un fragmento de HTML. Estas prácticas, si no se implementan correctamente, suelen terminar en objetos String de gran tamaño en el LOH que provocan constantes colecciones de basura completas y el correspondiente consumo de 100% CPU. Utilizad la clase StringBuilder para esto.

    ·         Cuidado con los objetos que cacheamos, y las referencias a otros objetos que estos pueden mantener. Cachear objetos indirectamente de forma “involuntaria” es la forma más frecuente de provocar un memory leak en aplicaciones .NET.

     

    Si queréis seguir profundizando en el funcionamiento del GC, os recomiendo los siguientes recursos:

     

    Garbage Collection: Automatic Memory Management in the Microsoft .NET Framework

    http://msdn.microsoft.com/en-us/magazine/bb985010.aspx

     

    Garbage Collection Part 2: Automatic Memory Management in the Microsoft .NET Framework

    http://msdn.microsoft.com/en-us/magazine/bb985011.aspx

     

    Garbage Collector Basics and Performance Hints

    http://msdn.microsoft.com/en-us/library/ms973837.aspx

     

    Maoni's WebLog - CLR Garbage Collector

    http://blogs.msdn.com/maoni/

     

    Hasta la próxima,

    - Daniel Mossberg

  • The code is out there

    ¿Son normales los errores HTTP 401.1 y 401.2 que veo en mis logs de IIS?

    Posted over 4 years ago
    by Daniel Mossberg
    • 0 Comments

    Si alguna vez os habéis molestado en analizar unos logs de IIS, quizá os hayais encontrado con el escenario en el que cuando tenemos habilitada la autenticación de Windows integrada, cada petición que hacemos realmente necesita tres peticiones para llevarse a cabo. Este es el patrón típico de la autenticación NTLM. La primera petición falla dando un error HTTP 401.2, la segunda falla también pero con un error 401.1, y la tercera es la definitiva devolviendo un resultado HTTP 200 - OK.

     

    Os explico el porqué de esta secuencia:

     

    SECUENCIA

    PETICIÓN

    RESPUESTA

    RESULTADO HTTP

    1

    El cliente (por ejemplo Internet Explorer) hace una petición GET (o POST) a un servidor IIS. Esta primera petición es anónima, puesto que a priori el cliente no tiene porqué saber nada sobre este servidor: no sabe si el servidor requiere algún tipo de autenticación, no sabe qué tipos de autenticación acepta el servidor, etc.


    Supongamos que el servidor sólo tiene habilitada la autenticación de Windows integrada. Puesto que la petición que ha llegado es anónima, el servidor responde con un error HTTP 401.2 (Logon Failed due to server configuration) y le especifica al cliente los métodos de autenticación que acepta. Para este ejemplo supongamos que acepta Negotiate y NTLM.

    401.2
    [Logon Failed due to server configuration]

    2

    El cliente  elige autenticarse mediante NTLM (el motivo de esta elección es irrelevante para el ejemplo) y le pasa un encabezado HTTP al servidor indicando que se va a autenticar por NTLM. En este encabezado indica también la cuenta de usuario con la que se va a autenticar (pero no la contraseña).


    Para que el servidor compruebe que el cliente es quién dice ser, necesita comprobar que conoce la contraseña sin pedírsela. Para ello genera un número aleatorio de 16-byte, conocido como el “challenge” (o reto) y se lo envía al cliente. En esta segunda petición, el servidor vuelve a responder con un error HTTP 401.1 (Logon Failed) puesto que el cliente todavía no está autenticado.

    401.1
    [Logon Failed]

    3

    El cliente cifra el “challenge” con un hash de la contraseña y genera lo que llamamos el “response”. De esta manera, demuestra que conoce dicha contraseña sin que la contraseña en ningún momento se intercambie por la red. Vuelve a enviar la misma petición GET por tercera (y última) vez y con el “response”.


    El servidor (que tampoco conoce la contraseña del cliente) envía los siguientes datos al controlador de dominio:

    ·         Nombre de usuario (del cliente)

    ·         El “challenge” enviado al cliente

    ·         El “response” generado por el cliente

    El controlador de dominio que sí conoce la contraseña del cliente, cifra el “challenge” con un hash de la contraseña del usuario igual que hizo IE y compara el resultado con el “response” que ha generado IE. Si todo es correcto, por fin se autentica al usuario y se le envía la respuesta definitiva (HTTP 200 –OK).

    200
    [OK]

     

    Una vez hemos visto el motivo de este intercambio de peticiones y respuestas HTTP, más de uno se pregunta ¿esto no genera un excesivo tráfico de red? Las respuesta es que depende. Evidentemente, el protocolo de autenticación NTLM genera más tráfico de red que un sitio web configurado con autenticación anónima, pero el tráfico adicional normalmente no supone un problema.

     

    Por un lado, todos los clientes HTTP que soportan autenticación NTLM implementan una optimización, que implica que en la segunda petición del handshake NTLM únicamente se incluyen los encabezados HTTP (y por lo tanto se excluye el HTTP entity-body, es decir los datos asociados a la petición en caso de que los haya). Os pongo un ejemplo, si la petición en cuestión es un POST de un fichero de 100MB, el primer POST anónimo (junto con los 100MB de fichero adjunto) va a fallar con un error 401.1. Cuando el cliente decide autenticarse por NTLM, hace un segundo POST pero esta vez sin entity-body (es decir, sin los 100MB de fichero adjunto) dado que ya sabe que esta petición también va a fallar inevitablemente con un error 401.1. En la última petición, de nuevo volvemos a incluir el entity-body, que será la petición definitiva y cuando finalmente logremos subir el fichero al servidor HTTP.

     

    Adicionalmente, Internet Explorer (en adelante IE) incluye una característica conocida como pre-autenticación NTLM que evita realizar la primera petición anónima una vez determina que un servidor en concreto acepta autenticación NTLM. La pre-autenticación NTLM directamente envía el encabezado HTTP de autenticación por NTLM (paso 2 de la secuencia) indicando al servidor el nombre de usuario para que este genere el “challenge”.

     

    Esta optimización puede dar problemas si una vez iniciado este comportamiento de IE, vamos a parar a un directorio virtual en el mismo sitio web que no admite autenticación NTLM (sino que únicamente utiliza autenticación anónima). En este caso, IE va a hacer una petición enviando los encabezados HTTP (especificando autenticación por NTLM) esperando un 401.1 por parte del servidor. Puesto que IE “cree” que la petición definitiva va a ser la siguiente, elige no mandar los datos del entity-body para ahorrar ancho de banda. Puesto que el servidor no acepta autenticación NTLM pero si acepta autenticación anónima, ignora el encabezado HTTP de autenticación y responde con un HTTP 200 definitivo. Esto provoca que, por ejemplo, los datos del formulario HTML (o el fichero de 100MB del ejemplo anterior) nunca se lleguen a enviar con el POST. El siguiente artículo habla precisamente de este problema:

     

    You cannot post data to a non-NTLM-authenticated Web site

    http://support.microsoft.com/kb/251404/en-us

     

    Si tenemos una aplicación ASP.NET que realiza llamadas a un web service, se autentica por NTLM y queremos evitar la primera petición anónima, podemos implementar la pre-autenticación NTLM de la siguiente manera:

     

    MyWebService proxy = new MyWebService();

    proxy.Credentials = CredentialCache.DefaultNetworkCredentials;

    proxy.PreAuthenticate = true;

    proxy.HelloWorld();

     

    Por último, si queremos reducir aún más el ancho de banda causado por la autenticación NTLM, podemos reducir los Custom Errrors que devuelve IIS para los errores HTTP 401.1 y 401.2.

     

    Happy hacking.

    - Daniel Mossberg 

  • The code is out there

    Cómo hacer más seguro un servidor IIS 6.0

    Posted over 4 years ago
    by Daniel Mossberg
    • 0 Comments

    Cuando Microsoft sacó Windows Server 2003 con IIS 6.0 se dio un gran paso adelante en cuanto al paradigma secure by default. A diferencia de anteriores versiones, IIS 6.0 se instalaba con muchas de sus funcionalidades deshabilitadas por defecto (p. ej. extensiones ISAPI y componentes CGI), de forma que cada administrador debía habilitar explícitamente las funcionalidades que realmente fuera a utilizar. En una instalación por defecto de IIS 6.0 sólo se sirve contenido estático.

     

    Con Windows Server 2008 e IIS 7.0 se ha dado un paso más en esta dirección, pero este tema ya lo trataré en un post separado.

     

    En todo caso, aunque la instalación por defecto de IIS 6.0 ya nos proporciona un buen punto de partida en cuanto a seguridad, podemos tomar una serie de precauciones para hacer nuestros servidor menos susceptibles a ser atacados, y en el peor de los casos, mitigar las consecuencias de un eventual ataque exitoso. Estas son algunas de las recomendaciones que damos a nuestros clientes (no están ordenadas por importancia):

     

    Reducir la superficie de ataque del servidor usando la herramienta Security Configuration Wizard for Windows Server 2003.
    Utilizar esta herramienta es una buena práctica para establecer un buen punto de partida del bastionado de los servidores IIS. Esta herramienta nos ayuda a determinar la funcionalidad mínima requerida para el rol o roles de nuestro servidor y nos permite:

    ·         Deshabilitar servicios no requeridos.

    ·         Bloquear puertos que nos están en uso.

    ·         Aplicar restricciones a los puertos que dejamos abiertos.

    ·         Deshabilitar las Web Extensions de IIS que no vayamos a necesitar.

    ·         Establecer políticas de auditoría eficaces.

    Utilizar Host Headers para los sitios web en IIS.
    El uso de Host Headers previene que un sitio web responda a cualquier otra URL que las que están configuradas como Host Headers. De esta forma podemos evitar ataques de escaneo de IPs. La mayoría de los gusanos se extienden mediante escaneo de IPs, por lo que podemos minimizar las susceptibilidad de nuestro servidor a estos ataques simplemente utilizando host headers.

     

    Almacenar el contenido del sitio web en una partición o unidad de disco distinta a la del sistema y auditar los permisos NTFS.
    Esto nos permitirá protegernos de ataques del tipo directory traversal o backtracking en los que el hacker intenta acceder a ficheros de sistema mediante rutas relativas para obtener el control de la máquina. Restringir al máximo los permisos NTFS del contenido del sitio web es igualmente muy importante.

    Desplegar los servidores IIS publicados en Internet fuera del dominio corporativo.
    Debido a la exposición de los frontales IIS a los ataques de Internet, siempre es recomendable tenerlos los más separados posible de la intranet corporativa y el dominio de directorio activo. De tal forma mitigaremos el potencial daño al resto de máquinas del dominio si la seguridad del servidor IIS se ve comprometida.

    Usar URLScan 3.1 para bloquear peticiones potencialmente peligrosas.

    Con URLScan podremos crear reglas para bloquear verbos HTTP específicos, extensiones de ficheros, secuencias de caracteres y aplicarlas de forma individual a uno o varios de los encabezados HTTP de cada petición.

     

    Por ejemplo, podemos crear reglas para mitigar ataques de inyección de SQL, de forma que las peticiones cuyo querystring (o cualquier otro encabezado HTTP relvante como las cookies) cumpla una serie de requisitos serán bloqueadas y nunca llegaran a procesarse por nuestro servidor web.

     

    La herramienta la podemos descargar desde aquí:
    UrlScan version 3.1 RTW - x86

    UrlScan version 3.1 RTW - x64

    Algunos recursos sobre la herramienta:

    Common URLScan Scenarios

    http://learn.iis.net/page.aspx/476/common-urlscan-scenarios/

     

    UrlScan 3.1

    http://blogs.iis.net/wadeh/archive/2008/10/31/urlscan-3-1.aspx

     

    Habilitar extended logging de IIS

    Los logs de IIS nos pueden proporcionar información valiosa sobre intentos de ataque y ataques exitosos a nuestros servidores web. Es recomendable habilitar extended logging de IIS configurado de modo que se registren todos los campos disponibles (por defecto no se registran todos). Adicionalmente, debemos almacenar los logs en una partición o unidad de disco distinta a la del contenido del sitio web y distinta a la del sistema operativo. Por último debemos restringir los permisos NTFS a la carpeta de logs.

    De esta forma dificultaremos la tarea del hacker a la hora de borrar sus huellas tras una ataque exitoso. Únicamente tendrán permisos full control sobre esta carpeta los administradores (y el sistema). Posteriormente debemos analizar regularmente dichos logs, lo cual podemos hacer, por ejemplo, utilizando Log Parser (ver post Análisis de logs de IIS utilizando Log Parser).


    Auditar regularmente la seguridad de los servidores mediante el Microsoft Baseline Security Analyzer

    Esta herramienta nos ayuda a identificar vulnerabilidades causadas por configuraciones y políticas inadecuadas, falta de actualizaciones de seguridad, y adicionalmente realiza una serie de comprobaciones específicas sobre la configuración de IIS.


    Microsoft Baseline Security Analyzer
    http://www.microsoft.com/technet/security/tools/mbsahome.mspx

     

    Espero que os sea de utilidad.

     

    - Daniel Mossberg

  • The code is out there

    ¿Con qué credenciales se ejecuta mi aplicación web?

    Posted over 4 years ago
    by Daniel Mossberg
    • 0 Comments

    Existen diversos escenarios en los que nos es útil saber qué método de autenticación está utilizando nuestra aplicación web y con qué credenciales se está ejecutando nuestro código. Para poder determinarlo de forma rápida he desarrollado una página ASP.NET que hace estas comprobaciones y muestra el resultado en pantalla.

    clip_image002

    Este es el código de la página ASPX:

     

    <%@ Page Language="C#" Debug="true" %>

     

    <%@ Import Namespace="System.Threading" %>

    <%@ Import Namespace="System.Security.Principal" %>

     

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"

        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

     

    <script runat="server">

     

        public string AuthType, AuthPackage, WindowsID, HttpContextID, ThreadID;

        private AuthTypeEnum _authType;

     

        internal enum AuthTypeEnum

        {

            Anonymous,

            Negotiate,

            NTLM,

            Other

        }

     

        protected void Page_Load(object sender, EventArgs e)

        {

            _authType = AuthTypeEnum.Other;

            GetIdentities();

            Response.Headers.Add("Connection", "Close");

        }

     

        private void GetIdentities()

        {

            AuthType = GetAuthType();

            AuthPackage = GetAuthPackage();

            WindowsID = GetWindowsID();       

            HttpContextID = GetHttpContextID();

            ThreadID = GetThreadID();

        }

     

        private string GetAuthType()

        {

            if (Context.User.Identity.AuthenticationType != String.Empty)

            {

                _authType = AuthTypeEnum.Negotiate;

                return Context.User.Identity.AuthenticationType;

            }

            else if (!Context.User.Identity.IsAuthenticated)

            {

                _authType = AuthTypeEnum.Anonymous;

                return "Not Authenticated (Anonymous)";

            }

            else

                return "-";

        }

     

        private string GetAuthPackage()

        {

            if (_authType != AuthTypeEnum.Anonymous &&

                Context.Request.ServerVariables["HTTP_AUTHORIZATION"] != null)

            {

                string authHeader =

                    Context.Request.ServerVariables["HTTP_AUTHORIZATION"];

                if (authHeader.StartsWith("Negotiate TlRMTVNTUA"))

                    return "Kerberos";

                else

                    return "NTLM";

            }

            else

                return "-";

        }

     

        private string GetWindowsID()

        {

            if (WindowsIdentity.GetCurrent().Name != String.Empty)

                return WindowsIdentity.GetCurrent().Name;

            else

                return "-";

        }

     

        private string GetHttpContextID()

        {

            if (HttpContext.Current.User.Identity.Name != String.Empty)

                return HttpContext.Current.User.Identity.Name;

            else

                return "-";

        }

     

        private string GetThreadID()

        {

            if (Thread.CurrentPrincipal.Identity.Name != String.Empty)

                return Thread.CurrentPrincipal.Identity.Name;

            else

                return "-";

        }

       

    </script>

     

    <html xmlns="http://www.w3.org/1999/xhtml">

    <head id="Head1" runat="server">

        <title>ASP.NET Identity Test</title>

        <style type="text/css">

            .style_div

            {

                font-family: "Consolas";

                font-size: 22px;

            }

            .left

            {

                font-weight: bold;

                width: 300px;

            }

            .right

            {

                color: #FF0000;

            }

        </style>

    </head>

    <body>

        <form id="form1" runat="server">

        <div class="style_div">

            <table border="0" cellspacing="0" cellpadding="0">

                <tr>

                    <td class="left">

                        Authentication Type:

                    </td>

                    <td class="right">

                        <% Response.Write(AuthType); %>

                    </td>

                </tr>

                <tr>

                    <td class="left">

                        Authentication Package:

                    </td>

                    <td class="right">

                        <% Response.Write(AuthPackage); %>

                    </td>

                </tr>

                <tr>

                    <td class="left">

                        Windows Identity:

                    </td>

                    <td class="right">

                        <% Response.Write(WindowsID); %>

                    </td>

                </tr>

                <tr>

                    <td class="left">

                        HttpContext Identity:

                    </td>

                    <td class="right">

                        <% Response.Write(HttpContextID); %>

                    </td>

                </tr>

                <tr>

                    <td class="left">

                        Thread Identity:

                    </td>

                    <td class="right">

                        <% Response.Write(ThreadID); %>

                    </td>

                </tr>

            </table>

        </div>

        </form>

    </body>

    </html>

     

    Espero que os sea de utilidad.

     

    - Daniel Mossberg

Page 1 of 2 (31 items) 12
  • © 2013 Microsoft Corporation.
  • Terms of Use
  • Trademarks
  • Privacy & Cookies
  • Report Abuse
  • 5.6.426.415