static MagickBooleanType sRGBTransformImage(Image *image,
  const ColorspaceType colorspace,ExceptionInfo *exception)
{
#define sRGBTransformImageTag  "RGBTransform/Image"

  CacheView
    *image_view;

  MagickBooleanType
    status;

  MagickOffsetType
    progress;

  PrimaryInfo
    primary_info;

  ssize_t
    i;

  ssize_t
    y;

  TransformPacket
    *x_map,
    *y_map,
    *z_map;

  assert(image != (Image *) NULL);
  assert(image->signature == MagickCoreSignature);
  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
  assert(colorspace != sRGBColorspace);
  assert(colorspace != TransparentColorspace);
  assert(colorspace != UndefinedColorspace);
  status=MagickTrue;
  progress=0;
  switch (colorspace)
  {
    case CMYKColorspace:
    {
      PixelInfo
        zero;

      /*
        Convert RGB to CMYK colorspace.
      */
      if (image->storage_class == PseudoClass)
        {
          if (SyncImage(image,exception) == MagickFalse)
            return(MagickFalse);
          if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
            return(MagickFalse);
        }
      if (SetImageColorspace(image,colorspace,exception) == MagickFalse)
        return(MagickFalse);
      GetPixelInfo(image,&zero);
      image_view=AcquireAuthenticCacheView(image,exception);
#if defined(MAGICKCORE_OPENMP_SUPPORT)
      #pragma omp parallel for schedule(static) shared(status) \
        magick_number_threads(image,image,image->rows,1)
#endif
      for (y=0; y < (ssize_t) image->rows; y++)
      {
        MagickBooleanType
          sync;

        PixelInfo
          pixel;

        ssize_t
          x;

        Quantum
          *magick_restrict q;

        if (status == MagickFalse)
          continue;
        q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
          exception);
        if (q == (Quantum *) NULL)
          {
            status=MagickFalse;
            continue;
          }
        pixel=zero;
        for (x=0; x < (ssize_t) image->columns; x++)
        {
          GetPixelInfoPixel(image,q,&pixel);
          ConvertRGBToCMYK(&pixel);
          SetPixelViaPixelInfo(image,&pixel,q);
          q+=GetPixelChannels(image);
        }
        sync=SyncCacheViewAuthenticPixels(image_view,exception);
        if (sync == MagickFalse)
          status=MagickFalse;
      }
      image_view=DestroyCacheView(image_view);
      image->type=image->alpha_trait == UndefinedPixelTrait ?
        ColorSeparationType : ColorSeparationAlphaType;
      if (SetImageColorspace(image,colorspace,exception) == MagickFalse)
        return(MagickFalse);
      return(status);
    }
    case LinearGRAYColorspace:
    {
      /*
        Transform image from sRGB to GRAY.
      */
      if (image->storage_class == PseudoClass)
        {
          if (SyncImage(image,exception) == MagickFalse)
            return(MagickFalse);
          if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
            return(MagickFalse);
        }
      image_view=AcquireAuthenticCacheView(image,exception);
#if defined(MAGICKCORE_OPENMP_SUPPORT)
      #pragma omp parallel for schedule(static) shared(status) \
        magick_number_threads(image,image,image->rows,1)
#endif
      for (y=0; y < (ssize_t) image->rows; y++)
      {
        MagickBooleanType
          sync;

        ssize_t
          x;

        Quantum
          *magick_restrict q;

        if (status == MagickFalse)
          continue;
        q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
          exception);
        if (q == (Quantum *) NULL)
          {
            status=MagickFalse;
            continue;
          }
        for (x=0; x < (ssize_t) image->columns; x++)
        {
          MagickRealType
            gray;

          gray=0.212656*GetPixelRed(image,q)+0.715158*GetPixelGreen(image,q)+
            0.072186*GetPixelBlue(image,q);
          SetPixelGray(image,ClampToQuantum(DecodePixelGamma(gray)),q);
          q+=GetPixelChannels(image);
        }
        sync=SyncCacheViewAuthenticPixels(image_view,exception);
        if (sync == MagickFalse)
          status=MagickFalse;
      }
      image_view=DestroyCacheView(image_view);
      if (SetImageColorspace(image,colorspace,exception) == MagickFalse)
        return(MagickFalse);
      image->type=GrayscaleType;
      return(status);
    }
    case GRAYColorspace:
    {
      /*
        Transform image from sRGB to GRAY.
      */
      if (image->storage_class == PseudoClass)
        {
          if (SyncImage(image,exception) == MagickFalse)
            return(MagickFalse);
          if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
            return(MagickFalse);
        }
      image_view=AcquireAuthenticCacheView(image,exception);
#if defined(MAGICKCORE_OPENMP_SUPPORT)
      #pragma omp parallel for schedule(static) shared(status) \
        magick_number_threads(image,image,image->rows,1)
#endif
      for (y=0; y < (ssize_t) image->rows; y++)
      {
        MagickBooleanType
          sync;

        ssize_t
          x;

        Quantum
          *magick_restrict q;

        if (status == MagickFalse)
          continue;
        q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
          exception);
        if (q == (Quantum *) NULL)
          {
            status=MagickFalse;
            continue;
          }
        for (x=0; x < (ssize_t) image->columns; x++)
        {
          MagickRealType
            gray;

          gray=0.212656*GetPixelRed(image,q)+0.715158*GetPixelGreen(image,q)+
            0.072186*GetPixelBlue(image,q);
          SetPixelGray(image,ClampToQuantum(gray),q);
          q+=GetPixelChannels(image);
        }
        sync=SyncCacheViewAuthenticPixels(image_view,exception);
        if (sync == MagickFalse)
          status=MagickFalse;
      }
      image_view=DestroyCacheView(image_view);
      if (SetImageColorspace(image,colorspace,exception) == MagickFalse)
        return(MagickFalse);
      image->type=GrayscaleType;
      return(status);
    }
    case CMYColorspace:
    case Adobe98Colorspace:
    case DisplayP3Colorspace:
    case HCLColorspace:
    case HCLpColorspace:
    case HSBColorspace:
    case HSIColorspace:
    case HSLColorspace:
    case HSVColorspace:
    case HWBColorspace:
    case JzazbzColorspace:
    case LabColorspace:
    case LCHColorspace:
    case LCHabColorspace:
    case LCHuvColorspace:
    case LMSColorspace:
    case LuvColorspace:
    case ProPhotoColorspace:
    case xyYColorspace:
    case XYZColorspace:
    case YCbCrColorspace:
    case YDbDrColorspace:
    case YIQColorspace:
    case YPbPrColorspace:
    case YUVColorspace:
    {
      const char
        *value;

      double
        white_luminance;

      /*
        Transform image from sRGB to target colorspace.
      */
      white_luminance=10000.0;
      value=GetImageProperty(image,"white-luminance",exception);
      if (value != (const char *) NULL)
        white_luminance=StringToDouble(value,(char **) NULL);
      if (image->storage_class == PseudoClass)
        {
          if (SyncImage(image,exception) == MagickFalse)
            return(MagickFalse);
          if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
            return(MagickFalse);
        }
      image_view=AcquireAuthenticCacheView(image,exception);
#if defined(MAGICKCORE_OPENMP_SUPPORT)
      #pragma omp parallel for schedule(static) shared(status) \
        magick_number_threads(image,image,image->rows,1)
#endif
      for (y=0; y < (ssize_t) image->rows; y++)
      {
        MagickBooleanType
          sync;

        ssize_t
          x;

        Quantum
          *magick_restrict q;

        if (status == MagickFalse)
          continue;
        q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
          exception);
        if (q == (Quantum *) NULL)
          {
            status=MagickFalse;
            continue;
          }
        for (x=0; x < (ssize_t) image->columns; x++)
        {
          double
            blue,
            green,
            red,
            X,
            Y,
            Z;

          red=(double) GetPixelRed(image,q);
          green=(double) GetPixelGreen(image,q);
          blue=(double) GetPixelBlue(image,q);
          switch (colorspace)
          {
            case Adobe98Colorspace:
            {
              ConvertRGBToAdobe98(red,green,blue,&X,&Y,&Z);
              break;
            }
            case CMYColorspace:
            {
              ConvertRGBToCMY(red,green,blue,&X,&Y,&Z);
              break;
            }
            case DisplayP3Colorspace:
            {
              ConvertRGBToDisplayP3(red,green,blue,&X,&Y,&Z);
              break;
            }
            case HCLColorspace:
            {
              ConvertRGBToHCL(red,green,blue,&X,&Y,&Z);
              break;
            }
            case HCLpColorspace:
            {
              ConvertRGBToHCLp(red,green,blue,&X,&Y,&Z);
              break;
            }
            case HSBColorspace:
            {
              ConvertRGBToHSB(red,green,blue,&X,&Y,&Z);
              break;
            }
            case HSIColorspace:
            {
              ConvertRGBToHSI(red,green,blue,&X,&Y,&Z);
              break;
            }
            case HSLColorspace:
            {
              ConvertRGBToHSL(red,green,blue,&X,&Y,&Z);
              break;
            }
            case HSVColorspace:
            {
              ConvertRGBToHSV(red,green,blue,&X,&Y,&Z);
              break;
            }
            case HWBColorspace:
            {
              ConvertRGBToHWB(red,green,blue,&X,&Y,&Z);
              break;
            }
            case JzazbzColorspace:
            {
              ConvertRGBToJzazbz(red,green,blue,white_luminance,&X,&Y,&Z);
              break;
            }
            case LabColorspace:
            {
              ConvertRGBToLab(red,green,blue,&X,&Y,&Z);
              break;
            }
            case LCHColorspace:
            case LCHabColorspace:
            {
              ConvertRGBToLCHab(red,green,blue,&X,&Y,&Z);
              break;
            }
            case LCHuvColorspace:
            {
              ConvertRGBToLCHuv(red,green,blue,&X,&Y,&Z);
              break;
            }
            case LMSColorspace:
            {
              ConvertRGBToLMS(red,green,blue,&X,&Y,&Z);
              break;
            }
            case LuvColorspace:
            {
              ConvertRGBToLuv(red,green,blue,&X,&Y,&Z);
              break;
            }
            case ProPhotoColorspace:
            {
              ConvertRGBToProPhoto(red,green,blue,&X,&Y,&Z);
              break;
            }
            case xyYColorspace:
            {
              ConvertRGBToxyY(red,green,blue,&X,&Y,&Z);
              break;
            }
            case XYZColorspace:
            {
              ConvertRGBToXYZ(red,green,blue,&X,&Y,&Z);
              break;
            }
            case YCbCrColorspace:
            {
              ConvertRGBToYCbCr(red,green,blue,&X,&Y,&Z);
              break;
            }
            case YDbDrColorspace:
            {
              ConvertRGBToYDbDr(red,green,blue,&X,&Y,&Z);
              break;
            }
            case YIQColorspace:
            {
              ConvertRGBToYIQ(red,green,blue,&X,&Y,&Z);
              break;
            }
            case YPbPrColorspace:
            {
              ConvertRGBToYPbPr(red,green,blue,&X,&Y,&Z);
              break;
            }
            case YUVColorspace:
            {
              ConvertRGBToYUV(red,green,blue,&X,&Y,&Z);
              break;
            }
            default:
            {
              X=QuantumScale*red;
              Y=QuantumScale*green;
              Z=QuantumScale*blue;
              break;
            }
          }
          SetPixelRed(image,ClampToQuantum(QuantumRange*X),q);
          SetPixelGreen(image,ClampToQuantum(QuantumRange*Y),q);
          SetPixelBlue(image,ClampToQuantum(QuantumRange*Z),q);
          q+=GetPixelChannels(image);
        }
        sync=SyncCacheViewAuthenticPixels(image_view,exception);
        if (sync == MagickFalse)
          status=MagickFalse;
      }
      image_view=DestroyCacheView(image_view);
      if (SetImageColorspace(image,colorspace,exception) == MagickFalse)
        return(MagickFalse);
      return(status);
    }
    case LogColorspace:
    {
#define DisplayGamma  (1.0/1.7)
#define FilmGamma  0.6
#define ReferenceBlack  95.0
#define ReferenceWhite  685.0

      const char
        *value;

      double
        black,
        density,
        film_gamma,
        gamma,
        reference_black,
        reference_white;

      Quantum
        *logmap;

      /*
        Transform RGB to Log colorspace.
      */
      density=DisplayGamma;
      gamma=DisplayGamma;
      value=GetImageProperty(image,"gamma",exception);
      if (value != (const char *) NULL)
        gamma=PerceptibleReciprocal(StringToDouble(value,(char **) NULL));
      film_gamma=FilmGamma;
      value=GetImageProperty(image,"film-gamma",exception);
      if (value != (const char *) NULL)
        film_gamma=StringToDouble(value,(char **) NULL);
      reference_black=ReferenceBlack;
      value=GetImageProperty(image,"reference-black",exception);
      if (value != (const char *) NULL)
        reference_black=StringToDouble(value,(char **) NULL);
      reference_white=ReferenceWhite;
      value=GetImageProperty(image,"reference-white",exception);
      if (value != (const char *) NULL)
        reference_white=StringToDouble(value,(char **) NULL);
      logmap=(Quantum *) AcquireQuantumMemory((size_t) MaxMap+1UL,
        sizeof(*logmap));
      if (logmap == (Quantum *) NULL)
        ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
          image->filename);
      black=pow(10.0,(reference_black-reference_white)*(gamma/density)*0.002*
        PerceptibleReciprocal(film_gamma));
#if defined(MAGICKCORE_OPENMP_SUPPORT)
      #pragma omp parallel for schedule(static)
#endif
      for (i=0; i <= (ssize_t) MaxMap; i++)
        logmap[i]=ScaleMapToQuantum((double) (MaxMap*(reference_white+
          log10(black+(1.0*i/MaxMap)*(1.0-black))/((gamma/density)*0.002*
          PerceptibleReciprocal(film_gamma)))/1024.0));
      image_view=AcquireAuthenticCacheView(image,exception);
#if defined(MAGICKCORE_OPENMP_SUPPORT)
      #pragma omp parallel for schedule(static) shared(status) \
        magick_number_threads(image,image,image->rows,1)
#endif
      for (y=0; y < (ssize_t) image->rows; y++)
      {
        MagickBooleanType
          sync;

        ssize_t
          x;

        Quantum
          *magick_restrict q;

        if (status == MagickFalse)
          continue;
        q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
          exception);
        if (q == (Quantum *) NULL)
          {
            status=MagickFalse;
            continue;
          }
        for (x=(ssize_t) image->columns; x != 0; x--)
        {
          double
            blue,
            green,
            red;

          red=(double) DecodePixelGamma((MagickRealType)
            GetPixelRed(image,q));
          green=(double) DecodePixelGamma((MagickRealType)
            GetPixelGreen(image,q));
          blue=(double) DecodePixelGamma((MagickRealType)
            GetPixelBlue(image,q));
          SetPixelRed(image,logmap[ScaleQuantumToMap(ClampToQuantum(red))],q);
          SetPixelGreen(image,logmap[ScaleQuantumToMap(ClampToQuantum(green))],
            q);
          SetPixelBlue(image,logmap[ScaleQuantumToMap(ClampToQuantum(blue))],q);
          q+=GetPixelChannels(image);
        }
        sync=SyncCacheViewAuthenticPixels(image_view,exception);
        if (sync == MagickFalse)
          status=MagickFalse;
      }
      image_view=DestroyCacheView(image_view);
      logmap=(Quantum *) RelinquishMagickMemory(logmap);
      if (SetImageColorspace(image,colorspace,exception) == MagickFalse)
        return(MagickFalse);
      return(status);
    }
    case RGBColorspace:
    case scRGBColorspace:
    {
      /*
        Transform image from sRGB to linear RGB.
      */
      if (image->storage_class == PseudoClass)
        {
          if (SyncImage(image,exception) == MagickFalse)
            return(MagickFalse);
          if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
            return(MagickFalse);
        }
      image_view=AcquireAuthenticCacheView(image,exception);
#if defined(MAGICKCORE_OPENMP_SUPPORT)
      #pragma omp parallel for schedule(static) shared(status) \
        magick_number_threads(image,image,image->rows,1)
#endif
      for (y=0; y < (ssize_t) image->rows; y++)
      {
        MagickBooleanType
          sync;

        ssize_t
          x;

        Quantum
          *magick_restrict q;

        if (status == MagickFalse)
          continue;
        q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
          exception);
        if (q == (Quantum *) NULL)
          {
            status=MagickFalse;
            continue;
          }
        for (x=0; x < (ssize_t) image->columns; x++)
        {
          double
            blue,
            green,
            red;

          red=DecodePixelGamma((MagickRealType) GetPixelRed(image,q));
          green=DecodePixelGamma((MagickRealType) GetPixelGreen(image,q));
          blue=DecodePixelGamma((MagickRealType) GetPixelBlue(image,q));
          SetPixelRed(image,ClampToQuantum(red),q);
          SetPixelGreen(image,ClampToQuantum(green),q);
          SetPixelBlue(image,ClampToQuantum(blue),q);
          q+=GetPixelChannels(image);
        }
        sync=SyncCacheViewAuthenticPixels(image_view,exception);
        if (sync == MagickFalse)
          status=MagickFalse;
      }
      image_view=DestroyCacheView(image_view);
      if (SetImageColorspace(image,colorspace,exception) == MagickFalse)
        return(MagickFalse);
      return(status);
    }
    default:
      break;
  }
  /*
    Allocate the tables.
  */
  x_map=(TransformPacket *) AcquireQuantumMemory((size_t) MaxMap+1UL,
    sizeof(*x_map));
  y_map=(TransformPacket *) AcquireQuantumMemory((size_t) MaxMap+1UL,
    sizeof(*y_map));
  z_map=(TransformPacket *) AcquireQuantumMemory((size_t) MaxMap+1UL,
    sizeof(*z_map));
  if ((x_map == (TransformPacket *) NULL) ||
      (y_map == (TransformPacket *) NULL) ||
      (z_map == (TransformPacket *) NULL))
    {
      if (x_map != (TransformPacket *) NULL)
        x_map=(TransformPacket *) RelinquishMagickMemory(x_map);
      if (y_map != (TransformPacket *) NULL)
        y_map=(TransformPacket *) RelinquishMagickMemory(y_map);
      if (z_map != (TransformPacket *) NULL)
        z_map=(TransformPacket *) RelinquishMagickMemory(z_map);
      ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
        image->filename);
    }
  (void) memset(&primary_info,0,sizeof(primary_info));
  switch (colorspace)
  {
    case OHTAColorspace:
    {
      /*
        Initialize OHTA tables:

          I1 = 0.33333*R+0.33334*G+0.33333*B
          I2 = 0.50000*R+0.00000*G-0.50000*B
          I3 =-0.25000*R+0.50000*G-0.25000*B

        I and Q, normally -0.5 through 0.5, are normalized to the range 0
        through QuantumRange.
      */
      primary_info.y=(double) (MaxMap+1.0)/2.0;
      primary_info.z=(double) (MaxMap+1.0)/2.0;
#if defined(MAGICKCORE_OPENMP_SUPPORT)
      #pragma omp parallel for schedule(static)
#endif
      for (i=0; i <= (ssize_t) MaxMap; i++)
      {
        x_map[i].x=(MagickRealType) (0.33333*(double) i);
        x_map[i].y=(MagickRealType) (0.50000*(double) i);
        x_map[i].z=(MagickRealType) (-0.25000*(double) i);
        y_map[i].x=(MagickRealType) (0.33334*(double) i);
        y_map[i].y=(MagickRealType) (0.00000*(double) i);
        y_map[i].z=(MagickRealType) (0.50000*(double) i);
        z_map[i].x=(MagickRealType) (0.33333*(double) i);
        z_map[i].y=(MagickRealType) (-0.50000*(double) i);
        z_map[i].z=(MagickRealType) (-0.25000*(double) i);
      }
      break;
    }
    case Rec601YCbCrColorspace:
    {
      /*
        Initialize YCbCr tables (ITU-R BT.601):

          Y =  0.2988390*R+0.5868110*G+0.1143500*B
          Cb= -0.1687367*R-0.3312640*G+0.5000000*B
          Cr=  0.5000000*R-0.4186880*G-0.0813120*B

        Cb and Cr, normally -0.5 through 0.5, are normalized to the range 0
        through QuantumRange.
      */
      primary_info.y=(double) (MaxMap+1.0)/2.0;
      primary_info.z=(double) (MaxMap+1.0)/2.0;
#if defined(MAGICKCORE_OPENMP_SUPPORT)
      #pragma omp parallel for schedule(static)
#endif
      for (i=0; i <= (ssize_t) MaxMap; i++)
      {
        x_map[i].x=(MagickRealType) (0.298839*(double) i);
        x_map[i].y=(MagickRealType) (-0.1687367*(double) i);
        x_map[i].z=(MagickRealType) (0.500000*(double) i);
        y_map[i].x=(MagickRealType) (0.586811*(double) i);
        y_map[i].y=(MagickRealType) (-0.331264*(double) i);
        y_map[i].z=(MagickRealType) (-0.418688*(double) i);
        z_map[i].x=(MagickRealType) (0.114350*(double) i);
        z_map[i].y=(MagickRealType) (0.500000*(double) i);
        z_map[i].z=(MagickRealType) (-0.081312*(double) i);
      }
      break;
    }
    case Rec709YCbCrColorspace:
    {
      /*
        Initialize YCbCr tables (ITU-R BT.709):

          Y =  0.212656*R+0.715158*G+0.072186*B
          Cb= -0.114572*R-0.385428*G+0.500000*B
          Cr=  0.500000*R-0.454153*G-0.045847*B

        Cb and Cr, normally -0.5 through 0.5, are normalized to the range 0
        through QuantumRange.
      */
      primary_info.y=(double) (MaxMap+1.0)/2.0;
      primary_info.z=(double) (MaxMap+1.0)/2.0;
#if defined(MAGICKCORE_OPENMP_SUPPORT)
      #pragma omp parallel for schedule(static)
#endif
      for (i=0; i <= (ssize_t) MaxMap; i++)
      {
        x_map[i].x=(MagickRealType) (0.212656*(double) i);
        x_map[i].y=(MagickRealType) (-0.114572*(double) i);
        x_map[i].z=(MagickRealType) (0.500000*(double) i);
        y_map[i].x=(MagickRealType) (0.715158*(double) i);
        y_map[i].y=(MagickRealType) (-0.385428*(double) i);
        y_map[i].z=(MagickRealType) (-0.454153*(double) i);
        z_map[i].x=(MagickRealType) (0.072186*(double) i);
        z_map[i].y=(MagickRealType) (0.500000*(double) i);
        z_map[i].z=(MagickRealType) (-0.045847*(double) i);
      }
      break;
    }
    case YCCColorspace:
    {
      /*
        Initialize YCC tables:

          Y =  0.298839*R+0.586811*G+0.114350*B
          C1= -0.298839*R-0.586811*G+0.88600*B
          C2=  0.70100*R-0.586811*G-0.114350*B

        YCC is scaled by 1.3584.  C1 zero is 156 and C2 is at 137.
      */
      primary_info.y=(double) ScaleQuantumToMap(ScaleCharToQuantum(156));
      primary_info.z=(double) ScaleQuantumToMap(ScaleCharToQuantum(137));
      for (i=0; i <= (ssize_t) (0.018*MaxMap); i++)
      {
        x_map[i].x=0.005382*i;
        x_map[i].y=(-0.003296)*i;
        x_map[i].z=0.009410*i;
        y_map[i].x=0.010566*i;
        y_map[i].y=(-0.006471)*i;
        y_map[i].z=(-0.007880)*i;
        z_map[i].x=0.002052*i;
        z_map[i].y=0.009768*i;
        z_map[i].z=(-0.001530)*i;
      }
      for ( ; i <= (ssize_t) MaxMap; i++)
      {
        x_map[i].x=0.298839*(1.099*i-0.099);
        x_map[i].y=(-0.298839)*(1.099*i-0.099);
        x_map[i].z=0.70100*(1.099*i-0.099);
        y_map[i].x=0.586811*(1.099*i-0.099);
        y_map[i].y=(-0.586811)*(1.099*i-0.099);
        y_map[i].z=(-0.586811)*(1.099*i-0.099);
        z_map[i].x=0.114350*(1.099*i-0.099);
        z_map[i].y=0.88600*(1.099*i-0.099);
        z_map[i].z=(-0.114350)*(1.099*i-0.099);
      }
      break;
    }
    default:
    {
      /*
        Linear conversion tables.
      */
#if defined(MAGICKCORE_OPENMP_SUPPORT)
      #pragma omp parallel for schedule(static)
#endif
      for (i=0; i <= (ssize_t) MaxMap; i++)
      {
        x_map[i].x=(MagickRealType) (1.0*(double) i);
        x_map[i].y=(MagickRealType) 0.0;
        x_map[i].z=(MagickRealType) 0.0;
        y_map[i].x=(MagickRealType) 0.0;
        y_map[i].y=(MagickRealType) (1.0*(double) i);
        y_map[i].z=(MagickRealType) 0.0;
        z_map[i].x=(MagickRealType) 0.0;
        z_map[i].y=(MagickRealType) 0.0;
        z_map[i].z=(MagickRealType) (1.0*(double) i);
      }
      break;
    }
  }
  /*
    Convert from sRGB.
  */
  switch (image->storage_class)
  {
    case DirectClass:
    default:
    {
      /*
        Convert DirectClass image.
      */
      image_view=AcquireAuthenticCacheView(image,exception);
#if defined(MAGICKCORE_OPENMP_SUPPORT)
      #pragma omp parallel for schedule(static) shared(status) \
        magick_number_threads(image,image,image->rows,1)
#endif
      for (y=0; y < (ssize_t) image->rows; y++)
      {
        MagickBooleanType
          sync;

        PixelInfo
          pixel;

        Quantum
          *magick_restrict q;

        ssize_t
          x;

        unsigned int
          blue,
          green,
          red;

        if (status == MagickFalse)
          continue;
        q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
          exception);
        if (q == (Quantum *) NULL)
          {
            status=MagickFalse;
            continue;
          }
        for (x=0; x < (ssize_t) image->columns; x++)
        {
          red=ScaleQuantumToMap(ClampToQuantum((MagickRealType)
            GetPixelRed(image,q)));
          green=ScaleQuantumToMap(ClampToQuantum((MagickRealType)
            GetPixelGreen(image,q)));
          blue=ScaleQuantumToMap(ClampToQuantum((MagickRealType)
            GetPixelBlue(image,q)));
          pixel.red=(x_map[red].x+y_map[green].x+z_map[blue].x)+
            primary_info.x;
          pixel.green=(x_map[red].y+y_map[green].y+z_map[blue].y)+
            primary_info.y;
          pixel.blue=(x_map[red].z+y_map[green].z+z_map[blue].z)+
            primary_info.z;
          SetPixelRed(image,ScaleMapToQuantum(pixel.red),q);
          SetPixelGreen(image,ScaleMapToQuantum(pixel.green),q);
          SetPixelBlue(image,ScaleMapToQuantum(pixel.blue),q);
          q+=GetPixelChannels(image);
        }
        sync=SyncCacheViewAuthenticPixels(image_view,exception);
        if (sync == MagickFalse)
          status=MagickFalse;
        if (image->progress_monitor != (MagickProgressMonitor) NULL)
          {
            MagickBooleanType
              proceed;

#if defined(MAGICKCORE_OPENMP_SUPPORT)
            #pragma omp atomic
#endif
            progress++;
            proceed=SetImageProgress(image,sRGBTransformImageTag,progress,
              image->rows);
            if (proceed == MagickFalse)
              status=MagickFalse;
          }
      }
      image_view=DestroyCacheView(image_view);
      break;
    }
    case PseudoClass:
    {
      unsigned int
        blue,
        green,
        red;

      /*
        Convert PseudoClass image.
      */
      for (i=0; i < (ssize_t) image->colors; i++)
      {
        PixelInfo
          pixel;

        red=ScaleQuantumToMap(ClampToQuantum(image->colormap[i].red));
        green=ScaleQuantumToMap(ClampToQuantum(image->colormap[i].green));
        blue=ScaleQuantumToMap(ClampToQuantum(image->colormap[i].blue));
        pixel.red=x_map[red].x+y_map[green].x+z_map[blue].x+primary_info.x;
        pixel.green=x_map[red].y+y_map[green].y+z_map[blue].y+primary_info.y;
        pixel.blue=x_map[red].z+y_map[green].z+z_map[blue].z+primary_info.z;
        image->colormap[i].red=(double) ScaleMapToQuantum(pixel.red);
        image->colormap[i].green=(double) ScaleMapToQuantum(pixel.green);
        image->colormap[i].blue=(double) ScaleMapToQuantum(pixel.blue);
      }
      (void) SyncImage(image,exception);
      break;
    }
  }
  /*
    Relinquish resources.
  */
  z_map=(TransformPacket *) RelinquishMagickMemory(z_map);
  y_map=(TransformPacket *) RelinquishMagickMemory(y_map);
  x_map=(TransformPacket *) RelinquishMagickMemory(x_map);
  if (SetImageColorspace(image,colorspace,exception) == MagickFalse)
    return(MagickFalse);
  return(status);
}


Fix buffer overflow if NULL line is present in db.

If ptr->line == NULL for an entry, the first cycle will exit,
but the second one will happily write past entries buffer.
We actually do not want to exit the first cycle prematurely
on ptr->line == NULL.
Signed-off-by: Tomas Mraz <tmraz@fedoraproject.org>

commonio_sort (struct commonio_db *db, int (*cmp) (const void *, const void *))
{
	struct commonio_entry **entries, *ptr;
	size_t n = 0, i;
#if KEEP_NIS_AT_END
	struct commonio_entry *nis = NULL;
#endif

	for (ptr = db->head;
	        (NULL != ptr)
#if KEEP_NIS_AT_END
	     && (NULL != ptr->line)
	     && (   ('+' != ptr->line[0])
	         && ('-' != ptr->line[0]))
#endif
	     ;
	     ptr = ptr->next) {
		n++;
	}
#if KEEP_NIS_AT_END
	if ((NULL != ptr) && (NULL != ptr->line)) {
		nis = ptr;
	}
#endif

	if (n <= 1) {
		return 0;
	}

	entries = malloc (n * sizeof (struct commonio_entry *));
	if (entries == NULL) {
		return -1;
	}

	n = 0;
	for (ptr = db->head;
#if KEEP_NIS_AT_END
	     nis != ptr;
#else
	     NULL != ptr;
#endif
/*@ -nullderef @*/
	     ptr = ptr->next
/*@ +nullderef @*/
	    ) {
		entries[n] = ptr;
		n++;
	}
	qsort (entries, n, sizeof (struct commonio_entry *), cmp);

	/* Take care of the head and tail separately */
	db->head = entries[0];
	n--;
#if KEEP_NIS_AT_END
	if (NULL == nis)
#endif
	{
		db->tail = entries[n];
	}
	db->head->prev = NULL;
	db->head->next = entries[1];
	entries[n]->prev = entries[n - 1];
#if KEEP_NIS_AT_END
	entries[n]->next = nis;
#else
	entries[n]->next = NULL;
#endif

	/* Now other elements have prev and next entries */
	for (i = 1; i < n; i++) {
		entries[i]->prev = entries[i - 1];
		entries[i]->next = entries[i + 1];
	}

	free (entries);
	db->changed = true;

	return 0;
}

In shadow before 4.5, the newusers tool could be made to manipulate internal data structures in ways unintended by the authors. Malformed input may lead to crashes (with a buffer overflow or other memory corruption) or other unspecified behaviors. This crosses a privilege boundary in, for example, certain web-hosting environments in which a Control Panel allows an unprivileged user account to create subaccounts.


commonio_sort (struct commonio_db *db, int (*cmp) (const void *, const void *))
{
	struct commonio_entry **entries, *ptr;
	size_t n = 0, i;
#if KEEP_NIS_AT_END
	struct commonio_entry *nis = NULL;
#endif

	for (ptr = db->head;
	        (NULL != ptr)
#if KEEP_NIS_AT_END
	     && ((NULL == ptr->line)
	         || (('+' != ptr->line[0])
	             && ('-' != ptr->line[0])))
#endif
	     ;
	     ptr = ptr->next) {
		n++;
	}
#if KEEP_NIS_AT_END
	if (NULL != ptr) {
		nis = ptr;
	}
#endif

	if (n <= 1) {
		return 0;
	}

	entries = malloc (n * sizeof (struct commonio_entry *));
	if (entries == NULL) {
		return -1;
	}

	n = 0;
	for (ptr = db->head;
#if KEEP_NIS_AT_END
	     nis != ptr;
#else
	     NULL != ptr;
#endif
/*@ -nullderef @*/
	     ptr = ptr->next
/*@ +nullderef @*/
	    ) {
		entries[n] = ptr;
		n++;
	}
	qsort (entries, n, sizeof (struct commonio_entry *), cmp);

	/* Take care of the head and tail separately */
	db->head = entries[0];
	n--;
#if KEEP_NIS_AT_END
	if (NULL == nis)
#endif
	{
		db->tail = entries[n];
	}
	db->head->prev = NULL;
	db->head->next = entries[1];
	entries[n]->prev = entries[n - 1];
#if KEEP_NIS_AT_END
	entries[n]->next = nis;
#else
	entries[n]->next = NULL;
#endif

	/* Now other elements have prev and next entries */
	for (i = 1; i < n; i++) {
		entries[i]->prev = entries[i - 1];
		entries[i]->next = entries[i + 1];
	}

	free (entries);
	db->changed = true;

	return 0;
}


CVE-2020-10730: lib ldb: Check if ldb_lock_backend_callback called twice

Prevent use after free issues if ldb_lock_backend_callback is called
twice, usually due to ldb_module_done being called twice. This can happen if a
module ignores the return value from function a function that calls
ldb_module_done as part of it's error handling.

BUG: https://bugzilla.samba.org/show_bug.cgi?id=14364

Signed-off-by: Gary Lockyer <gary@catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>

static int ldb_lock_backend_callback(struct ldb_request *req,
				     struct ldb_reply *ares)
{
	struct ldb_db_lock_context *lock_context;
	int ret;

	lock_context = talloc_get_type(req->context,
				       struct ldb_db_lock_context);

	if (!ares) {
		return ldb_module_done(lock_context->req, NULL, NULL,
					LDB_ERR_OPERATIONS_ERROR);
	}
	if (ares->error != LDB_SUCCESS || ares->type == LDB_REPLY_DONE) {
		ret = ldb_module_done(lock_context->req, ares->controls,
				      ares->response, ares->error);
		/*
		 * If this is a LDB_REPLY_DONE or an error, unlock the
		 * DB by calling the destructor on this context
		 */
		talloc_free(lock_context);
		return ret;
	}

	/* Otherwise pass on the callback */
	switch (ares->type) {
	case LDB_REPLY_ENTRY:
		return ldb_module_send_entry(lock_context->req, ares->message,
					     ares->controls);

	case LDB_REPLY_REFERRAL:
		return ldb_module_send_referral(lock_context->req,
						ares->referral);
	default:
		/* Can't happen */
		return LDB_ERR_OPERATIONS_ERROR;
	}
}

A NULL pointer dereference, or possible use-after-free flaw was found in Samba AD LDAP server in versions before 4.10.17, before 4.11.11 and before 4.12.4. Although some versions of Samba shipped with Red Hat Enterprise Linux do not support Samba in AD mode, the affected code is shipped with the libldb package. This flaw allows an authenticated user to possibly trigger a use-after-free or NULL pointer dereference. The highest threat from this vulnerability is to system availability.


static int ldb_lock_backend_callback(struct ldb_request *req,
				     struct ldb_reply *ares)
{
	struct ldb_db_lock_context *lock_context;
	int ret;

	if (req->context == NULL) {
		/*
		 * The usual way to get here is to ignore the return codes
		 * and continuing processing after an error.
		 */
		abort();
	}
	lock_context = talloc_get_type(req->context,
				       struct ldb_db_lock_context);

	if (!ares) {
		return ldb_module_done(lock_context->req, NULL, NULL,
					LDB_ERR_OPERATIONS_ERROR);
	}
	if (ares->error != LDB_SUCCESS || ares->type == LDB_REPLY_DONE) {
		ret = ldb_module_done(lock_context->req, ares->controls,
				      ares->response, ares->error);
		/*
		 * If this is a LDB_REPLY_DONE or an error, unlock the
		 * DB by calling the destructor on this context
		 */
		TALLOC_FREE(req->context);
		return ret;
	}

	/* Otherwise pass on the callback */
	switch (ares->type) {
	case LDB_REPLY_ENTRY:
		return ldb_module_send_entry(lock_context->req, ares->message,
					     ares->controls);

	case LDB_REPLY_REFERRAL:
		return ldb_module_send_referral(lock_context->req,
						ares->referral);
	default:
		/* Can't happen */
		return LDB_ERR_OPERATIONS_ERROR;
	}
}


tcp: tcp_fragment() should apply sane memory limits

Jonathan Looney reported that a malicious peer can force a sender
to fragment its retransmit queue into tiny skbs, inflating memory
usage and/or overflow 32bit counters.

TCP allows an application to queue up to sk_sndbuf bytes,
so we need to give some allowance for non malicious splitting
of retransmit queue.

A new SNMP counter is added to monitor how many times TCP
did not allow to split an skb if the allowance was exceeded.

Note that this counter might increase in the case applications
use SO_SNDBUF socket option to lower sk_sndbuf.

CVE-2019-11478 : tcp_fragment, prevent fragmenting a packet when the
	socket is already using more than half the allowed space

Signed-off-by: Eric Dumazet <edumazet@google.com>
Reported-by: Jonathan Looney <jtl@netflix.com>
Acked-by: Neal Cardwell <ncardwell@google.com>
Acked-by: Yuchung Cheng <ycheng@google.com>
Reviewed-by: Tyler Hicks <tyhicks@canonical.com>
Cc: Bruce Curtis <brucec@netflix.com>
Cc: Jonathan Lemon <jonathan.lemon@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>

int tcp_fragment(struct sock *sk, enum tcp_queue tcp_queue,
		 struct sk_buff *skb, u32 len,
		 unsigned int mss_now, gfp_t gfp)
{
	struct tcp_sock *tp = tcp_sk(sk);
	struct sk_buff *buff;
	int nsize, old_factor;
	int nlen;
	u8 flags;

	if (WARN_ON(len > skb->len))
		return -EINVAL;

	nsize = skb_headlen(skb) - len;
	if (nsize < 0)
		nsize = 0;

	if (skb_unclone(skb, gfp))
		return -ENOMEM;

	/* Get a new skb... force flag on. */
	buff = sk_stream_alloc_skb(sk, nsize, gfp, true);
	if (!buff)
		return -ENOMEM; /* We'll just try again later. */

	sk->sk_wmem_queued += buff->truesize;
	sk_mem_charge(sk, buff->truesize);
	nlen = skb->len - len - nsize;
	buff->truesize += nlen;
	skb->truesize -= nlen;

	/* Correct the sequence numbers. */
	TCP_SKB_CB(buff)->seq = TCP_SKB_CB(skb)->seq + len;
	TCP_SKB_CB(buff)->end_seq = TCP_SKB_CB(skb)->end_seq;
	TCP_SKB_CB(skb)->end_seq = TCP_SKB_CB(buff)->seq;

	/* PSH and FIN should only be set in the second packet. */
	flags = TCP_SKB_CB(skb)->tcp_flags;
	TCP_SKB_CB(skb)->tcp_flags = flags & ~(TCPHDR_FIN | TCPHDR_PSH);
	TCP_SKB_CB(buff)->tcp_flags = flags;
	TCP_SKB_CB(buff)->sacked = TCP_SKB_CB(skb)->sacked;
	tcp_skb_fragment_eor(skb, buff);

	skb_split(skb, buff, len);

	buff->ip_summed = CHECKSUM_PARTIAL;

	buff->tstamp = skb->tstamp;
	tcp_fragment_tstamp(skb, buff);

	old_factor = tcp_skb_pcount(skb);

	/* Fix up tso_factor for both original and new SKB.  */
	tcp_set_skb_tso_segs(skb, mss_now);
	tcp_set_skb_tso_segs(buff, mss_now);

	/* Update delivered info for the new segment */
	TCP_SKB_CB(buff)->tx = TCP_SKB_CB(skb)->tx;

	/* If this packet has been sent out already, we must
	 * adjust the various packet counters.
	 */
	if (!before(tp->snd_nxt, TCP_SKB_CB(buff)->end_seq)) {
		int diff = old_factor - tcp_skb_pcount(skb) -
			tcp_skb_pcount(buff);

		if (diff)
			tcp_adjust_pcount(sk, skb, diff);
	}

	/* Link BUFF into the send queue. */
	__skb_header_release(buff);
	tcp_insert_write_queue_after(skb, buff, sk, tcp_queue);
	if (tcp_queue == TCP_FRAG_IN_RTX_QUEUE)
		list_add(&buff->tcp_tsorted_anchor, &skb->tcp_tsorted_anchor);

	return 0;
}

Jonathan Looney discovered that the TCP retransmission queue implementation in tcp_fragment in the Linux kernel could be fragmented when handling certain TCP Selective Acknowledgment (SACK) sequences. A remote attacker could use this to cause a denial of service. This has been fixed in stable kernel releases 4.4.182, 4.9.182, 4.14.127, 4.19.52, 5.1.11, and is fixed in commit f070ef2ac66716357066b683fb0baf55f8191a2e.


int tcp_fragment(struct sock *sk, enum tcp_queue tcp_queue,
		 struct sk_buff *skb, u32 len,
		 unsigned int mss_now, gfp_t gfp)
{
	struct tcp_sock *tp = tcp_sk(sk);
	struct sk_buff *buff;
	int nsize, old_factor;
	int nlen;
	u8 flags;

	if (WARN_ON(len > skb->len))
		return -EINVAL;

	nsize = skb_headlen(skb) - len;
	if (nsize < 0)
		nsize = 0;

	if (unlikely((sk->sk_wmem_queued >> 1) > sk->sk_sndbuf)) {
		NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPWQUEUETOOBIG);
		return -ENOMEM;
	}

	if (skb_unclone(skb, gfp))
		return -ENOMEM;

	/* Get a new skb... force flag on. */
	buff = sk_stream_alloc_skb(sk, nsize, gfp, true);
	if (!buff)
		return -ENOMEM; /* We'll just try again later. */

	sk->sk_wmem_queued += buff->truesize;
	sk_mem_charge(sk, buff->truesize);
	nlen = skb->len - len - nsize;
	buff->truesize += nlen;
	skb->truesize -= nlen;

	/* Correct the sequence numbers. */
	TCP_SKB_CB(buff)->seq = TCP_SKB_CB(skb)->seq + len;
	TCP_SKB_CB(buff)->end_seq = TCP_SKB_CB(skb)->end_seq;
	TCP_SKB_CB(skb)->end_seq = TCP_SKB_CB(buff)->seq;

	/* PSH and FIN should only be set in the second packet. */
	flags = TCP_SKB_CB(skb)->tcp_flags;
	TCP_SKB_CB(skb)->tcp_flags = flags & ~(TCPHDR_FIN | TCPHDR_PSH);
	TCP_SKB_CB(buff)->tcp_flags = flags;
	TCP_SKB_CB(buff)->sacked = TCP_SKB_CB(skb)->sacked;
	tcp_skb_fragment_eor(skb, buff);

	skb_split(skb, buff, len);

	buff->ip_summed = CHECKSUM_PARTIAL;

	buff->tstamp = skb->tstamp;
	tcp_fragment_tstamp(skb, buff);

	old_factor = tcp_skb_pcount(skb);

	/* Fix up tso_factor for both original and new SKB.  */
	tcp_set_skb_tso_segs(skb, mss_now);
	tcp_set_skb_tso_segs(buff, mss_now);

	/* Update delivered info for the new segment */
	TCP_SKB_CB(buff)->tx = TCP_SKB_CB(skb)->tx;

	/* If this packet has been sent out already, we must
	 * adjust the various packet counters.
	 */
	if (!before(tp->snd_nxt, TCP_SKB_CB(buff)->end_seq)) {
		int diff = old_factor - tcp_skb_pcount(skb) -
			tcp_skb_pcount(buff);

		if (diff)
			tcp_adjust_pcount(sk, skb, diff);
	}

	/* Link BUFF into the send queue. */
	__skb_header_release(buff);
	tcp_insert_write_queue_after(skb, buff, sk, tcp_queue);
	if (tcp_queue == TCP_FRAG_IN_RTX_QUEUE)
		list_add(&buff->tcp_tsorted_anchor, &skb->tcp_tsorted_anchor);

	return 0;
}


__zzip_parse_root_directory(int fd,
                            struct _disk_trailer *trailer,
                            struct zzip_dir_hdr **hdr_return,
                            zzip_plugin_io_t io,
                            zzip_off_t filesize)
{
    auto struct zzip_disk_entry dirent;
    struct zzip_dir_hdr *hdr;
    struct zzip_dir_hdr *hdr0;
    uint16_t *p_reclen = 0;
    zzip_off64_t entries;
    zzip_off64_t zz_offset;     /* offset from start of root directory */
    char *fd_map = 0;
    zzip_off64_t zz_fd_gap = 0;
    zzip_off64_t zz_entries = _disk_trailer_localentries(trailer);
    zzip_off64_t zz_rootsize = _disk_trailer_rootsize(trailer);
    zzip_off64_t zz_rootseek = _disk_trailer_rootseek(trailer);
    __correct_rootseek(zz_rootseek, zz_rootsize, trailer);

    if (zz_entries <= 0 || zz_rootsize < 0 ||
        zz_rootseek < 0 || zz_rootseek >= filesize)
        return ZZIP_CORRUPTED;

    hdr0 = (struct zzip_dir_hdr *) malloc(zz_rootsize);
    if (! hdr0)
        return ZZIP_DIRSIZE;
    hdr = hdr0;
    __debug_dir_hdr(hdr);

    if (USE_MMAP && io->fd.sys)
    {
        zz_fd_gap = zz_rootseek & (_zzip_getpagesize(io->fd.sys) - 1);
        HINT4(" fd_gap=%ld, mapseek=0x%lx, maplen=%ld", (long) (zz_fd_gap),
              (long) (zz_rootseek - zz_fd_gap),
              (long) (zz_rootsize + zz_fd_gap));
        fd_map =
            _zzip_mmap(io->fd.sys, fd, zz_rootseek - zz_fd_gap,
                       zz_rootsize + zz_fd_gap);
        /* if mmap failed we will fallback to seek/read mode */
        if (fd_map == MAP_FAILED)
        {
            NOTE2("map failed: %s", strerror(errno));
            fd_map = 0;
        } else
        {
            HINT3("mapped *%p len=%li", fd_map,
                  (long) (zz_rootsize + zz_fd_gap));
        }
    }

    for (entries=0, zz_offset=0; ; entries++)
    {
        register struct zzip_disk_entry *d;
        uint16_t u_extras, u_comment, u_namlen;

#     ifndef ZZIP_ALLOW_MODULO_ENTRIES
        if (entries >= zz_entries) {
            if (zz_offset + 256 < zz_rootsize) {
                FAIL4("%li's entry is long before the end of directory - enable modulo_entries? (O:%li R:%li)",
                      (long) entries, (long) (zz_offset), (long) zz_rootsize);
            }
            break;
        }
#     endif

        if (fd_map)
        {
            d = (void*)(fd_map+zz_fd_gap+zz_offset); /* fd_map+fd_gap==u_rootseek */
        } else
        {
            if (io->fd.seeks(fd, zz_rootseek + zz_offset, SEEK_SET) < 0)
	    {
	    	free(hdr0);
                return ZZIP_DIR_SEEK;
	    }
            if (io->fd.read(fd, &dirent, sizeof(dirent)) < __sizeof(dirent))
	    {
	    	free(hdr0);
                return ZZIP_DIR_READ;
	    }
            d = &dirent;
        }

        if ((zzip_off64_t) (zz_offset + sizeof(*d)) > zz_rootsize ||
            (zzip_off64_t) (zz_offset + sizeof(*d)) < 0)
        {
            FAIL4("%li's entry stretches beyond root directory (O:%li R:%li)",
                  (long) entries, (long) (zz_offset), (long) zz_rootsize);
            break;
        }

        if (! zzip_disk_entry_check_magic(d)) {
#        ifndef ZZIP_ALLOW_MODULO_ENTRIES
            FAIL4("%li's entry has no disk_entry magic indicator (O:%li R:%li)",
                  (long) entries, (long) (zz_offset), (long) zz_rootsize);
#        endif
            break;
        }

#       if 0 && defined DEBUG
        zzip_debug_xbuf((unsigned char *) d, sizeof(*d) + 8);
#       endif

        u_extras = zzip_disk_entry_get_extras(d);
        u_comment = zzip_disk_entry_get_comment(d);
        u_namlen = zzip_disk_entry_get_namlen(d);
        HINT5("offset=0x%lx, size %ld, dirent *%p, hdr %p\n",
              (long) (zz_offset + zz_rootseek), (long) zz_rootsize, d, hdr);

        /* writes over the read buffer, Since the structure where data is
           copied is smaller than the data in buffer this can be done.
           It is important that the order of setting the fields is considered
           when filling the structure, so that some data is not trashed in
           first structure read.
           at the end the whole copied list of structures  is copied into
           newly allocated buffer */
        hdr->d_crc32 = zzip_disk_entry_get_crc32(d);
        hdr->d_csize = zzip_disk_entry_get_csize(d);
        hdr->d_usize = zzip_disk_entry_get_usize(d);
        hdr->d_off = zzip_disk_entry_get_offset(d);
        hdr->d_compr = zzip_disk_entry_get_compr(d);
        if (hdr->d_compr > _255)
            hdr->d_compr = 255;

        if ((zzip_off64_t) (zz_offset + sizeof(*d) + u_namlen) > zz_rootsize ||
            (zzip_off64_t) (zz_offset + sizeof(*d) + u_namlen) < 0)
        {
            FAIL4("%li's name stretches beyond root directory (O:%li N:%li)",
                  (long) entries, (long) (zz_offset), (long) (u_namlen));
            break;
        }

        if (fd_map)
            {  memcpy(hdr->d_name, fd_map+zz_fd_gap + zz_offset+sizeof(*d), u_namlen); }
        else
            { io->fd.read(fd, hdr->d_name, u_namlen); }
        hdr->d_name[u_namlen] = '\0';
        hdr->d_namlen = u_namlen;

        /* update offset by the total length of this entry -> next entry */
        zz_offset += sizeof(*d) + u_namlen + u_extras + u_comment;

        if (zz_offset > zz_rootsize)
        {
            FAIL3("%li's entry stretches beyond root directory (O:%li)",
                  (long) entries, (long) (zz_offset));
            entries ++;
            break;
        }

        HINT5("file %ld { compr=%d crc32=$%x offset=%d",
              (long) entries, hdr->d_compr, hdr->d_crc32, hdr->d_off);
        HINT5("csize=%d usize=%d namlen=%d extras=%d",
              hdr->d_csize, hdr->d_usize, u_namlen, u_extras);
        HINT5("comment=%d name='%s' %s <sizeof %d> } ",
              u_comment, hdr->d_name, "", (int) sizeof(*d));

        p_reclen = &hdr->d_reclen;

        {
            register char *p = (char *) hdr;
            register char *q = aligned4(p + sizeof(*hdr) + u_namlen + 1);
            *p_reclen = (uint16_t) (q - p);
            hdr = (struct zzip_dir_hdr *) q;
        }
    }                           /*for */

    if (USE_MMAP && fd_map)
    {
        HINT3("unmap *%p len=%li", fd_map, (long) (zz_rootsize + zz_fd_gap));
        _zzip_munmap(io->fd.sys, fd_map, zz_rootsize + zz_fd_gap);
    }

    if (p_reclen)
    {
        *p_reclen = 0;          /* mark end of list */

        if (hdr_return)
            *hdr_return = hdr0;
	else
	{
	    /* If it is not assigned to *hdr_return, it will never be free()'d */
	    free(hdr0);
	}
    }                           /* else zero (sane) entries */
#  ifndef ZZIP_ALLOW_MODULO_ENTRIES
    return (entries != zz_entries) ? ZZIP_CORRUPTED : 0;
#  else
    return ((entries & (unsigned)0xFFFF) != zz_entries) ? ZZIP_CORRUPTED : 0;
#  endif
}

An issue was discovered in ZZIPlib through 0.13.69. There is a memory leak triggered in the function __zzip_parse_root_directory in zip.c, which will lead to a denial of service attack.


__zzip_parse_root_directory(int fd,
                            struct _disk_trailer *trailer,
                            struct zzip_dir_hdr **hdr_return,
                            zzip_plugin_io_t io,
                            zzip_off_t filesize)
{
    auto struct zzip_disk_entry dirent;
    struct zzip_dir_hdr *hdr;
    struct zzip_dir_hdr *hdr0;
    uint16_t *p_reclen = 0;
    zzip_off64_t entries;
    zzip_off64_t zz_offset;     /* offset from start of root directory */
    char *fd_map = 0;
    zzip_off64_t zz_fd_gap = 0;
    zzip_off64_t zz_entries = _disk_trailer_localentries(trailer);
    zzip_off64_t zz_rootsize = _disk_trailer_rootsize(trailer);
    zzip_off64_t zz_rootseek = _disk_trailer_rootseek(trailer);
    __correct_rootseek(zz_rootseek, zz_rootsize, trailer);

    if (zz_entries <= 0 || zz_rootsize < 0 ||
        zz_rootseek < 0 || zz_rootseek >= filesize)
        return ZZIP_CORRUPTED;

    hdr0 = (struct zzip_dir_hdr *) malloc(zz_rootsize);
    if (! hdr0)
        return ZZIP_DIRSIZE;
    hdr = hdr0;
    __debug_dir_hdr(hdr);

    if (USE_MMAP && io->fd.sys)
    {
        zz_fd_gap = zz_rootseek & (_zzip_getpagesize(io->fd.sys) - 1);
        HINT4(" fd_gap=%ld, mapseek=0x%lx, maplen=%ld", (long) (zz_fd_gap),
              (long) (zz_rootseek - zz_fd_gap),
              (long) (zz_rootsize + zz_fd_gap));
        fd_map =
            _zzip_mmap(io->fd.sys, fd, zz_rootseek - zz_fd_gap,
                       zz_rootsize + zz_fd_gap);
        /* if mmap failed we will fallback to seek/read mode */
        if (fd_map == MAP_FAILED)
        {
            NOTE2("map failed: %s", strerror(errno));
            fd_map = 0;
        } else
        {
            HINT3("mapped *%p len=%li", fd_map,
                  (long) (zz_rootsize + zz_fd_gap));
        }
    }

    for (entries=0, zz_offset=0; ; entries++)
    {
        register struct zzip_disk_entry *d;
        uint16_t u_extras, u_comment, u_namlen;

#     ifndef ZZIP_ALLOW_MODULO_ENTRIES
        if (entries >= zz_entries) {
            if (zz_offset + 256 < zz_rootsize) {
                FAIL4("%li's entry is long before the end of directory - enable modulo_entries? (O:%li R:%li)",
                      (long) entries, (long) (zz_offset), (long) zz_rootsize);
            }
            break;
        }
#     endif

        if (fd_map)
        {
            d = (void*)(fd_map+zz_fd_gap+zz_offset); /* fd_map+fd_gap==u_rootseek */
        } else
        {
            if (io->fd.seeks(fd, zz_rootseek + zz_offset, SEEK_SET) < 0)
	    {
	    	free(hdr0);
                return ZZIP_DIR_SEEK;
	    }
            if (io->fd.read(fd, &dirent, sizeof(dirent)) < __sizeof(dirent))
	    {
	    	free(hdr0);
                return ZZIP_DIR_READ;
	    }
            d = &dirent;
        }

        if ((zzip_off64_t) (zz_offset + sizeof(*d)) > zz_rootsize ||
            (zzip_off64_t) (zz_offset + sizeof(*d)) < 0)
        {
            FAIL4("%li's entry stretches beyond root directory (O:%li R:%li)",
                  (long) entries, (long) (zz_offset), (long) zz_rootsize);
            break;
        }

        if (! zzip_disk_entry_check_magic(d)) {
#        ifndef ZZIP_ALLOW_MODULO_ENTRIES
            FAIL4("%li's entry has no disk_entry magic indicator (O:%li R:%li)",
                  (long) entries, (long) (zz_offset), (long) zz_rootsize);
#        endif
            break;
        }

#       if 0 && defined DEBUG
        zzip_debug_xbuf((unsigned char *) d, sizeof(*d) + 8);
#       endif

        u_extras = zzip_disk_entry_get_extras(d);
        u_comment = zzip_disk_entry_get_comment(d);
        u_namlen = zzip_disk_entry_get_namlen(d);
        HINT5("offset=0x%lx, size %ld, dirent *%p, hdr %p\n",
              (long) (zz_offset + zz_rootseek), (long) zz_rootsize, d, hdr);

        /* writes over the read buffer, Since the structure where data is
           copied is smaller than the data in buffer this can be done.
           It is important that the order of setting the fields is considered
           when filling the structure, so that some data is not trashed in
           first structure read.
           at the end the whole copied list of structures  is copied into
           newly allocated buffer */
        hdr->d_crc32 = zzip_disk_entry_get_crc32(d);
        hdr->d_csize = zzip_disk_entry_get_csize(d);
        hdr->d_usize = zzip_disk_entry_get_usize(d);
        hdr->d_off = zzip_disk_entry_get_offset(d);
        hdr->d_compr = zzip_disk_entry_get_compr(d);
        if (hdr->d_compr > _255)
            hdr->d_compr = 255;

        if ((zzip_off64_t) (zz_offset + sizeof(*d) + u_namlen) > zz_rootsize ||
            (zzip_off64_t) (zz_offset + sizeof(*d) + u_namlen) < 0)
        {
            FAIL4("%li's name stretches beyond root directory (O:%li N:%li)",
                  (long) entries, (long) (zz_offset), (long) (u_namlen));
            break;
        }

        if (fd_map)
            {  memcpy(hdr->d_name, fd_map+zz_fd_gap + zz_offset+sizeof(*d), u_namlen); }
        else
            { io->fd.read(fd, hdr->d_name, u_namlen); }
        hdr->d_name[u_namlen] = '\0';
        hdr->d_namlen = u_namlen;

        /* update offset by the total length of this entry -> next entry */
        zz_offset += sizeof(*d) + u_namlen + u_extras + u_comment;

        if (zz_offset > zz_rootsize)
        {
            FAIL3("%li's entry stretches beyond root directory (O:%li)",
                  (long) entries, (long) (zz_offset));
            entries ++;
            break;
        }

        HINT5("file %ld { compr=%d crc32=$%x offset=%d",
              (long) entries, hdr->d_compr, hdr->d_crc32, hdr->d_off);
        HINT5("csize=%d usize=%d namlen=%d extras=%d",
              hdr->d_csize, hdr->d_usize, u_namlen, u_extras);
        HINT5("comment=%d name='%s' %s <sizeof %d> } ",
              u_comment, hdr->d_name, "", (int) sizeof(*d));

        p_reclen = &hdr->d_reclen;

        {
            register char *p = (char *) hdr;
            register char *q = aligned4(p + sizeof(*hdr) + u_namlen + 1);
            *p_reclen = (uint16_t) (q - p);
            hdr = (struct zzip_dir_hdr *) q;
        }
    }                           /*for */

    if (USE_MMAP && fd_map)
    {
        HINT3("unmap *%p len=%li", fd_map, (long) (zz_rootsize + zz_fd_gap));
        _zzip_munmap(io->fd.sys, fd_map, zz_rootsize + zz_fd_gap);
    }

    if (p_reclen)
    {
        *p_reclen = 0;          /* mark end of list */

        if (hdr_return)
            *hdr_return = hdr0;
	else
	{
	    /* If it is not assigned to *hdr_return, it will never be free()'d */
	    free(hdr0);
	}
    }                           /* else zero (sane) entries */
    else
        free(hdr0);
#  ifndef ZZIP_ALLOW_MODULO_ENTRIES
    return (entries != zz_entries) ? ZZIP_CORRUPTED : 0;
#  else
    return ((entries & (unsigned)0xFFFF) != zz_entries) ? ZZIP_CORRUPTED : 0;
#  endif
}


Fix host_name_lookup (Close 2747)

Thanks to Nico R for providing a reproducing configuration.

        host_lookup             = *
        message_size_limit      = ${if def:sender_host_name {32M}{32M}}
        acl_smtp_connect        = acl_smtp_connect
        acl_smtp_rcpt           = acl_smtp_rcpt

        begin acl
          acl_smtp_connect:
                warn ratelimit = 256 / 1m / per_conn
                accept

          acl_smtp_rcpt:
                accept hosts = 127.0.0.*

        begin routers
        null:
          driver          = accept
          transport       = null

        begin transports
        null:
          driver          = appendfile
          file            = /dev/null

Tested with

        swaks -f mailbox@example.org -t mailbox@example.org --pipe 'exim -bh 127.0.0.1 -C /opt/exim/etc/exim-bug.conf'

The IP must have a PTR to "localhost." to reproduce it.

(cherry picked from commit 20812729e3e47a193a21d326ecd036d67a8b2724)

host_name_lookup(void)
{
int old_pool, rc;
int sep = 0;
uschar *save_hostname;
uschar **aliases;
uschar *ordername;
const uschar *list = host_lookup_order;
dns_answer * dnsa = store_get_dns_answer();
dns_scan dnss;

sender_host_dnssec = host_lookup_deferred = host_lookup_failed = FALSE;

HDEBUG(D_host_lookup)
  debug_printf("looking up host name for %s\n", sender_host_address);

/* For testing the case when a lookup does not complete, we have a special
reserved IP address. */

if (f.running_in_test_harness &&
    Ustrcmp(sender_host_address, "99.99.99.99") == 0)
  {
  HDEBUG(D_host_lookup)
    debug_printf("Test harness: host name lookup returns DEFER\n");
  host_lookup_deferred = TRUE;
  return DEFER;
  }

/* Do lookups directly in the DNS or via gethostbyaddr() (or equivalent), in
the order specified by the host_lookup_order option. */

while ((ordername = string_nextinlist(&list, &sep, NULL, 0)))
  {
  if (strcmpic(ordername, US"bydns") == 0)
    {
    uschar * name = dns_build_reverse(sender_host_address);

    dns_init(FALSE, FALSE, FALSE);    /* dnssec ctrl by dns_dnssec_ok glbl */
    rc = dns_lookup_timerwrap(dnsa, name, T_PTR, NULL);

    /* The first record we come across is used for the name; others are
    considered to be aliases. We have to scan twice, in order to find out the
    number of aliases. However, if all the names are empty, we will behave as
    if failure. (PTR records that yield empty names have been encountered in
    the DNS.) */

    if (rc == DNS_SUCCEED)
      {
      uschar **aptr = NULL;
      int ssize = 264;
      int count = 0;
      int old_pool = store_pool;

      sender_host_dnssec = dns_is_secure(dnsa);
      DEBUG(D_dns)
        debug_printf("Reverse DNS security status: %s\n",
            sender_host_dnssec ? "DNSSEC verified (AD)" : "unverified");

      store_pool = POOL_PERM;        /* Save names in permanent storage */

      for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS);
           rr;
           rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)) if (rr->type == T_PTR)
	count++;

      /* Get store for the list of aliases. For compatibility with
      gethostbyaddr, we make an empty list if there are none. */

      aptr = sender_host_aliases = store_get(count * sizeof(uschar *), FALSE);

      /* Re-scan and extract the names */

      for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS);
           rr;
           rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)) if (rr->type == T_PTR)
        {
        uschar * s = store_get(ssize, TRUE);	/* names are tainted */

        /* If an overlong response was received, the data will have been
        truncated and dn_expand may fail. */

        if (dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen,
             US (rr->data), (DN_EXPAND_ARG4_TYPE)(s), ssize) < 0)
          {
          log_write(0, LOG_MAIN, "host name alias list truncated for %s",
            sender_host_address);
          break;
          }

        store_release_above(s + Ustrlen(s) + 1);
        if (!s[0])
          {
          HDEBUG(D_host_lookup) debug_printf("IP address lookup yielded an "
            "empty name: treated as non-existent host name\n");
          continue;
          }
        if (!sender_host_name) sender_host_name = s;
	else *aptr++ = s;
        while (*s) { *s = tolower(*s); s++; }
        }

      *aptr = NULL;            /* End of alias list */
      store_pool = old_pool;   /* Reset store pool */

      /* If we've found a name, break out of the "order" loop */

      if (sender_host_name) break;
      }

    /* If the DNS lookup deferred, we must also defer. */

    if (rc == DNS_AGAIN)
      {
      HDEBUG(D_host_lookup)
        debug_printf("IP address PTR lookup gave temporary error\n");
      host_lookup_deferred = TRUE;
      return DEFER;
      }
    }

  /* Do a lookup using gethostbyaddr() - or equivalent */

  else if (strcmpic(ordername, US"byaddr") == 0)
    {
    HDEBUG(D_host_lookup)
      debug_printf("IP address lookup using gethostbyaddr()\n");
    rc = host_name_lookup_byaddr();
    if (rc == DEFER)
      {
      host_lookup_deferred = TRUE;
      return rc;                       /* Can't carry on */
      }
    if (rc == OK) break;               /* Found a name */
    }
  }      /* Loop for bydns/byaddr scanning */

/* If we have failed to find a name, return FAIL and log when required.
NB host_lookup_msg must be in permanent store.  */

if (!sender_host_name)
  {
  if (host_checking || !f.log_testing_mode)
    log_write(L_host_lookup_failed, LOG_MAIN, "no host name found for IP "
      "address %s", sender_host_address);
  host_lookup_msg = US" (failed to find host name from IP address)";
  host_lookup_failed = TRUE;
  return FAIL;
  }

HDEBUG(D_host_lookup)
  {
  uschar **aliases = sender_host_aliases;
  debug_printf("IP address lookup yielded \"%s\"\n", sender_host_name);
  while (*aliases != NULL) debug_printf("  alias \"%s\"\n", *aliases++);
  }

/* We need to verify that a forward lookup on the name we found does indeed
correspond to the address. This is for security: in principle a malefactor who
happened to own a reverse zone could set it to point to any names at all.

This code was present in versions of Exim before 3.20. At that point I took it
out because I thought that gethostbyaddr() did the check anyway. It turns out
that this isn't always the case, so it's coming back in at 4.01. This version
is actually better, because it also checks aliases.

The code was made more robust at release 4.21. Prior to that, it accepted all
the names if any of them had the correct IP address. Now the code checks all
the names, and accepts only those that have the correct IP address. */

save_hostname = sender_host_name;   /* Save for error messages */
aliases = sender_host_aliases;
for (uschar * hname = sender_host_name; hname; hname = *aliases++)
  {
  int rc;
  BOOL ok = FALSE;
  host_item h = { .next = NULL, .name = hname, .mx = MX_NONE, .address = NULL };
  dnssec_domains d =
    { .request = sender_host_dnssec ? US"*" : NULL, .require = NULL };

  if (  (rc = host_find_bydns(&h, NULL, HOST_FIND_BY_A | HOST_FIND_BY_AAAA,
	  NULL, NULL, NULL, &d, NULL, NULL)) == HOST_FOUND
     || rc == HOST_FOUND_LOCAL
     )
    {
    HDEBUG(D_host_lookup) debug_printf("checking addresses for %s\n", hname);

    /* If the forward lookup was not secure we cancel the is-secure variable */

    DEBUG(D_dns) debug_printf("Forward DNS security status: %s\n",
	  h.dnssec == DS_YES ? "DNSSEC verified (AD)" : "unverified");
    if (h.dnssec != DS_YES) sender_host_dnssec = FALSE;

    for (host_item * hh = &h; hh; hh = hh->next)
      if (host_is_in_net(hh->address, sender_host_address, 0))
        {
        HDEBUG(D_host_lookup) debug_printf("  %s OK\n", hh->address);
        ok = TRUE;
        break;
        }
      else
        HDEBUG(D_host_lookup) debug_printf("  %s\n", hh->address);

    if (!ok) HDEBUG(D_host_lookup)
      debug_printf("no IP address for %s matched %s\n", hname,
        sender_host_address);
    }
  else if (rc == HOST_FIND_AGAIN)
    {
    HDEBUG(D_host_lookup) debug_printf("temporary error for host name lookup\n");
    host_lookup_deferred = TRUE;
    sender_host_name = NULL;
    return DEFER;
    }
  else
    HDEBUG(D_host_lookup) debug_printf("no IP addresses found for %s\n", hname);

  /* If this name is no good, and it's the sender name, set it null pro tem;
  if it's an alias, just remove it from the list. */

  if (!ok)
    {
    if (hname == sender_host_name) sender_host_name = NULL; else
      {
      uschar **a;                              /* Don't amalgamate - some */
      a = --aliases;                           /* compilers grumble */
      while (*a != NULL) { *a = a[1]; a++; }
      }
    }
  }

/* If sender_host_name == NULL, it means we didn't like the name. Replace
it with the first alias, if there is one. */

if (sender_host_name == NULL && *sender_host_aliases != NULL)
  sender_host_name = *sender_host_aliases++;

/* If we now have a main name, all is well. */

if (sender_host_name != NULL) return OK;

/* We have failed to find an address that matches. */

HDEBUG(D_host_lookup)
  debug_printf("%s does not match any IP address for %s\n",
    sender_host_address, save_hostname);

/* This message must be in permanent store */

old_pool = store_pool;
store_pool = POOL_PERM;
host_lookup_msg = string_sprintf(" (%s does not match any IP address for %s)",
  sender_host_address, save_hostname);
store_pool = old_pool;
host_lookup_failed = TRUE;
return FAIL;
}

Exim before 4.95 has a heap-based buffer overflow for the alias list in host_name_lookup in host.c when sender_host_name is set.


host_name_lookup(void)
{
int old_pool, rc;
int sep = 0;
uschar *save_hostname;
uschar **aliases;
uschar *ordername;
const uschar *list = host_lookup_order;
dns_answer * dnsa = store_get_dns_answer();
dns_scan dnss;

sender_host_dnssec = host_lookup_deferred = host_lookup_failed = FALSE;

HDEBUG(D_host_lookup)
  debug_printf("looking up host name for %s\n", sender_host_address);

/* For testing the case when a lookup does not complete, we have a special
reserved IP address. */

if (f.running_in_test_harness &&
    Ustrcmp(sender_host_address, "99.99.99.99") == 0)
  {
  HDEBUG(D_host_lookup)
    debug_printf("Test harness: host name lookup returns DEFER\n");
  host_lookup_deferred = TRUE;
  return DEFER;
  }

/* Do lookups directly in the DNS or via gethostbyaddr() (or equivalent), in
the order specified by the host_lookup_order option. */

while ((ordername = string_nextinlist(&list, &sep, NULL, 0)))
  {
  if (strcmpic(ordername, US"bydns") == 0)
    {
    uschar * name = dns_build_reverse(sender_host_address);

    dns_init(FALSE, FALSE, FALSE);    /* dnssec ctrl by dns_dnssec_ok glbl */
    rc = dns_lookup_timerwrap(dnsa, name, T_PTR, NULL);

    /* The first record we come across is used for the name; others are
    considered to be aliases. We have to scan twice, in order to find out the
    number of aliases. However, if all the names are empty, we will behave as
    if failure. (PTR records that yield empty names have been encountered in
    the DNS.) */

    if (rc == DNS_SUCCEED)
      {
      uschar **aptr = NULL;
      int ssize = 264;
      int count = 1;  /* need 1 more for terminating NULL */
      int old_pool = store_pool;

      sender_host_dnssec = dns_is_secure(dnsa);
      DEBUG(D_dns)
        debug_printf("Reverse DNS security status: %s\n",
            sender_host_dnssec ? "DNSSEC verified (AD)" : "unverified");

      store_pool = POOL_PERM;        /* Save names in permanent storage */

      for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS);
           rr;
           rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)) if (rr->type == T_PTR)
	count++;

      /* Get store for the list of aliases. For compatibility with
      gethostbyaddr, we make an empty list if there are none. */

      aptr = sender_host_aliases = store_get(count * sizeof(uschar *), FALSE);

      /* Re-scan and extract the names */

      for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS);
           rr;
           rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)) if (rr->type == T_PTR)
        {
        uschar * s = store_get(ssize, TRUE);	/* names are tainted */

        /* If an overlong response was received, the data will have been
        truncated and dn_expand may fail. */

        if (dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen,
             US (rr->data), (DN_EXPAND_ARG4_TYPE)(s), ssize) < 0)
          {
          log_write(0, LOG_MAIN, "host name alias list truncated for %s",
            sender_host_address);
          break;
          }

        store_release_above(s + Ustrlen(s) + 1);
        if (!s[0])
          {
          HDEBUG(D_host_lookup) debug_printf("IP address lookup yielded an "
            "empty name: treated as non-existent host name\n");
          continue;
          }
        if (!sender_host_name) sender_host_name = s;
	else *aptr++ = s;
        while (*s) { *s = tolower(*s); s++; }
        }

      *aptr = NULL;            /* End of alias list */
      store_pool = old_pool;   /* Reset store pool */

      /* If we've found a name, break out of the "order" loop */

      if (sender_host_name) break;
      }

    /* If the DNS lookup deferred, we must also defer. */

    if (rc == DNS_AGAIN)
      {
      HDEBUG(D_host_lookup)
        debug_printf("IP address PTR lookup gave temporary error\n");
      host_lookup_deferred = TRUE;
      return DEFER;
      }
    }

  /* Do a lookup using gethostbyaddr() - or equivalent */

  else if (strcmpic(ordername, US"byaddr") == 0)
    {
    HDEBUG(D_host_lookup)
      debug_printf("IP address lookup using gethostbyaddr()\n");
    rc = host_name_lookup_byaddr();
    if (rc == DEFER)
      {
      host_lookup_deferred = TRUE;
      return rc;                       /* Can't carry on */
      }
    if (rc == OK) break;               /* Found a name */
    }
  }      /* Loop for bydns/byaddr scanning */

/* If we have failed to find a name, return FAIL and log when required.
NB host_lookup_msg must be in permanent store.  */

if (!sender_host_name)
  {
  if (host_checking || !f.log_testing_mode)
    log_write(L_host_lookup_failed, LOG_MAIN, "no host name found for IP "
      "address %s", sender_host_address);
  host_lookup_msg = US" (failed to find host name from IP address)";
  host_lookup_failed = TRUE;
  return FAIL;
  }

HDEBUG(D_host_lookup)
  {
  uschar **aliases = sender_host_aliases;
  debug_printf("IP address lookup yielded \"%s\"\n", sender_host_name);
  while (*aliases != NULL) debug_printf("  alias \"%s\"\n", *aliases++);
  }

/* We need to verify that a forward lookup on the name we found does indeed
correspond to the address. This is for security: in principle a malefactor who
happened to own a reverse zone could set it to point to any names at all.

This code was present in versions of Exim before 3.20. At that point I took it
out because I thought that gethostbyaddr() did the check anyway. It turns out
that this isn't always the case, so it's coming back in at 4.01. This version
is actually better, because it also checks aliases.

The code was made more robust at release 4.21. Prior to that, it accepted all
the names if any of them had the correct IP address. Now the code checks all
the names, and accepts only those that have the correct IP address. */

save_hostname = sender_host_name;   /* Save for error messages */
aliases = sender_host_aliases;
for (uschar * hname = sender_host_name; hname; hname = *aliases++)
  {
  int rc;
  BOOL ok = FALSE;
  host_item h = { .next = NULL, .name = hname, .mx = MX_NONE, .address = NULL };
  dnssec_domains d =
    { .request = sender_host_dnssec ? US"*" : NULL, .require = NULL };

  if (  (rc = host_find_bydns(&h, NULL, HOST_FIND_BY_A | HOST_FIND_BY_AAAA,
	  NULL, NULL, NULL, &d, NULL, NULL)) == HOST_FOUND
     || rc == HOST_FOUND_LOCAL
     )
    {
    HDEBUG(D_host_lookup) debug_printf("checking addresses for %s\n", hname);

    /* If the forward lookup was not secure we cancel the is-secure variable */

    DEBUG(D_dns) debug_printf("Forward DNS security status: %s\n",
	  h.dnssec == DS_YES ? "DNSSEC verified (AD)" : "unverified");
    if (h.dnssec != DS_YES) sender_host_dnssec = FALSE;

    for (host_item * hh = &h; hh; hh = hh->next)
      if (host_is_in_net(hh->address, sender_host_address, 0))
        {
        HDEBUG(D_host_lookup) debug_printf("  %s OK\n", hh->address);
        ok = TRUE;
        break;
        }
      else
        HDEBUG(D_host_lookup) debug_printf("  %s\n", hh->address);

    if (!ok) HDEBUG(D_host_lookup)
      debug_printf("no IP address for %s matched %s\n", hname,
        sender_host_address);
    }
  else if (rc == HOST_FIND_AGAIN)
    {
    HDEBUG(D_host_lookup) debug_printf("temporary error for host name lookup\n");
    host_lookup_deferred = TRUE;
    sender_host_name = NULL;
    return DEFER;
    }
  else
    HDEBUG(D_host_lookup) debug_printf("no IP addresses found for %s\n", hname);

  /* If this name is no good, and it's the sender name, set it null pro tem;
  if it's an alias, just remove it from the list. */

  if (!ok)
    {
    if (hname == sender_host_name) sender_host_name = NULL; else
      {
      uschar **a;                              /* Don't amalgamate - some */
      a = --aliases;                           /* compilers grumble */
      while (*a != NULL) { *a = a[1]; a++; }
      }
    }
  }

/* If sender_host_name == NULL, it means we didn't like the name. Replace
it with the first alias, if there is one. */

if (sender_host_name == NULL && *sender_host_aliases != NULL)
  sender_host_name = *sender_host_aliases++;

/* If we now have a main name, all is well. */

if (sender_host_name != NULL) return OK;

/* We have failed to find an address that matches. */

HDEBUG(D_host_lookup)
  debug_printf("%s does not match any IP address for %s\n",
    sender_host_address, save_hostname);

/* This message must be in permanent store */

old_pool = store_pool;
store_pool = POOL_PERM;
host_lookup_msg = string_sprintf(" (%s does not match any IP address for %s)",
  sender_host_address, save_hostname);
store_pool = old_pool;
host_lookup_failed = TRUE;
return FAIL;
}


Prevent infinite recursion in HTMLlineproc0

Bug-Debian: https://github.com/tats/w3m/issues/36

HTMLtagproc1(struct parsed_tag *tag, struct html_feed_environ *h_env)
{
    char *p, *q, *r;
    int i, w, x, y, z, count, width;
    struct readbuffer *obuf = h_env->obuf;
    struct environment *envs = h_env->envs;
    Str tmp;
    int hseq;
    int cmd;
#ifdef ID_EXT
    char *id = NULL;
#endif				/* ID_EXT */

    cmd = tag->tagid;

    if (obuf->flag & RB_PRE) {
	switch (cmd) {
	case HTML_NOBR:
	case HTML_N_NOBR:
	case HTML_PRE_INT:
	case HTML_N_PRE_INT:
	    return 1;
	}
    }

    switch (cmd) {
    case HTML_B:
	obuf->in_bold++;
	if (obuf->in_bold > 1)
	    return 1;
	return 0;
    case HTML_N_B:
	if (obuf->in_bold == 1 && close_effect0(obuf, HTML_B))
	    obuf->in_bold = 0;
	if (obuf->in_bold > 0) {
	    obuf->in_bold--;
	    if (obuf->in_bold == 0)
		return 0;
	}
	return 1;
    case HTML_I:
	obuf->in_italic++;
	if (obuf->in_italic > 1)
	    return 1;
	return 0;
    case HTML_N_I:
	if (obuf->in_italic == 1 && close_effect0(obuf, HTML_I))
	    obuf->in_italic = 0;
	if (obuf->in_italic > 0) {
	    obuf->in_italic--;
	    if (obuf->in_italic == 0)
		return 0;
	}
	return 1;
    case HTML_U:
	obuf->in_under++;
	if (obuf->in_under > 1)
	    return 1;
	return 0;
    case HTML_N_U:
	if (obuf->in_under == 1 && close_effect0(obuf, HTML_U))
	    obuf->in_under = 0;
	if (obuf->in_under > 0) {
	    obuf->in_under--;
	    if (obuf->in_under == 0)
		return 0;
	}
	return 1;
    case HTML_EM:
	HTMLlineproc1("", h_env);
	return 1;
    case HTML_N_EM:
	HTMLlineproc1("", h_env);
	return 1;
    case HTML_STRONG:
	HTMLlineproc1("", h_env);
	return 1;
    case HTML_N_STRONG:
	HTMLlineproc1("", h_env);
	return 1;
    case HTML_Q:
	HTMLlineproc1("`", h_env);
	return 1;
    case HTML_N_Q:
	HTMLlineproc1("'", h_env);
	return 1;
    case HTML_P:
    case HTML_N_P:
	CLOSE_A;
	if (!(obuf->flag & RB_IGNORE_P)) {
	    flushline(h_env, obuf, envs[h_env->envc].indent, 1, h_env->limit);
	    do_blankline(h_env, obuf, envs[h_env->envc].indent, 0,
			 h_env->limit);
	}
	obuf->flag |= RB_IGNORE_P;
	if (cmd == HTML_P) {
	    set_alignment(obuf, tag);
	    obuf->flag |= RB_P;
	}
	return 1;
    case HTML_BR:
	flushline(h_env, obuf, envs[h_env->envc].indent, 1, h_env->limit);
	h_env->blank_lines = 0;
	return 1;
    case HTML_H:
	if (!(obuf->flag & (RB_PREMODE | RB_IGNORE_P))) {
	    flushline(h_env, obuf, envs[h_env->envc].indent, 0, h_env->limit);
	    do_blankline(h_env, obuf, envs[h_env->envc].indent, 0,
			 h_env->limit);
	}
	HTMLlineproc1("", h_env);
	set_alignment(obuf, tag);
	return 1;
    case HTML_N_H:
	HTMLlineproc1("", h_env);
	if (!(obuf->flag & RB_PREMODE)) {
	    flushline(h_env, obuf, envs[h_env->envc].indent, 0, h_env->limit);
	}
	do_blankline(h_env, obuf, envs[h_env->envc].indent, 0, h_env->limit);
	RB_RESTORE_FLAG(obuf);
	close_anchor(h_env, obuf);
	obuf->flag |= RB_IGNORE_P;
	return 1;
    case HTML_UL:
    case HTML_OL:
    case HTML_BLQ:
	CLOSE_A;
	if (!(obuf->flag & RB_IGNORE_P)) {
	    flushline(h_env, obuf, envs[h_env->envc].indent, 0, h_env->limit);
	    if (!(obuf->flag & RB_PREMODE) &&
		(h_env->envc == 0 || cmd == HTML_BLQ))
		do_blankline(h_env, obuf, envs[h_env->envc].indent, 0,
			     h_env->limit);
	}
	PUSH_ENV(cmd);
	if (cmd == HTML_UL || cmd == HTML_OL) {
	    if (parsedtag_get_value(tag, ATTR_START, &count)) {
		envs[h_env->envc].count = count - 1;
	    }
	}
	if (cmd == HTML_OL) {
	    envs[h_env->envc].type = '1';
	    if (parsedtag_get_value(tag, ATTR_TYPE, &p)) {
		envs[h_env->envc].type = (int)*p;
	    }
	}
	if (cmd == HTML_UL)
	    envs[h_env->envc].type = ul_type(tag, 0);
	flushline(h_env, obuf, envs[h_env->envc].indent, 0, h_env->limit);
	return 1;
    case HTML_N_UL:
    case HTML_N_OL:
    case HTML_N_DL:
    case HTML_N_BLQ:
	CLOSE_DT;
	CLOSE_A;
	if (h_env->envc > 0) {
	    flushline(h_env, obuf, envs[h_env->envc - 1].indent, 0,
		      h_env->limit);
	    POP_ENV;
	    if (!(obuf->flag & RB_PREMODE) &&
		(h_env->envc == 0 || cmd == HTML_N_DL || cmd == HTML_N_BLQ)) {
		do_blankline(h_env, obuf,
			     envs[h_env->envc].indent,
			     INDENT_INCR, h_env->limit);
		obuf->flag |= RB_IGNORE_P;
	    }
	}
	close_anchor(h_env, obuf);
	return 1;
    case HTML_DL:
	CLOSE_A;
	if (!(obuf->flag & RB_IGNORE_P)) {
	    flushline(h_env, obuf, envs[h_env->envc].indent, 0, h_env->limit);
	    if (!(obuf->flag & RB_PREMODE))
		do_blankline(h_env, obuf, envs[h_env->envc].indent, 0,
			     h_env->limit);
	}
	PUSH_ENV(cmd);
	if (parsedtag_exists(tag, ATTR_COMPACT))
	    envs[h_env->envc].env = HTML_DL_COMPACT;
	obuf->flag |= RB_IGNORE_P;
	return 1;
    case HTML_LI:
	CLOSE_A;
	CLOSE_DT;
	if (h_env->envc > 0) {
	    Str num;
	    flushline(h_env, obuf,
		      envs[h_env->envc - 1].indent, 0, h_env->limit);
	    envs[h_env->envc].count++;
	    if (parsedtag_get_value(tag, ATTR_VALUE, &p)) {
		count = atoi(p);
		if (count > 0)
		    envs[h_env->envc].count = count;
		else
		    envs[h_env->envc].count = 0;
	    }
	    switch (envs[h_env->envc].env) {
	    case HTML_UL:
		envs[h_env->envc].type = ul_type(tag, envs[h_env->envc].type);
		for (i = 0; i < INDENT_INCR - 3; i++)
		    push_charp(obuf, 1, NBSP, PC_ASCII);
		tmp = Strnew();
		switch (envs[h_env->envc].type) {
		case 'd':
		    push_symbol(tmp, UL_SYMBOL_DISC, symbol_width, 1);
		    break;
		case 'c':
		    push_symbol(tmp, UL_SYMBOL_CIRCLE, symbol_width, 1);
		    break;
		case 's':
		    push_symbol(tmp, UL_SYMBOL_SQUARE, symbol_width, 1);
		    break;
		default:
		    push_symbol(tmp,
				UL_SYMBOL((h_env->envc_real -
					   1) % MAX_UL_LEVEL), symbol_width,
				1);
		    break;
		}
		if (symbol_width == 1)
		    push_charp(obuf, 1, NBSP, PC_ASCII);
		push_str(obuf, symbol_width, tmp, PC_ASCII);
		push_charp(obuf, 1, NBSP, PC_ASCII);
		set_space_to_prevchar(obuf->prevchar);
		break;
	    case HTML_OL:
		if (parsedtag_get_value(tag, ATTR_TYPE, &p))
		    envs[h_env->envc].type = (int)*p;
		switch ((envs[h_env->envc].count > 0)? envs[h_env->envc].type: '1') {
		case 'i':
		    num = romanNumeral(envs[h_env->envc].count);
		    break;
		case 'I':
		    num = romanNumeral(envs[h_env->envc].count);
		    Strupper(num);
		    break;
		case 'a':
		    num = romanAlphabet(envs[h_env->envc].count);
		    break;
		case 'A':
		    num = romanAlphabet(envs[h_env->envc].count);
		    Strupper(num);
		    break;
		default:
		    num = Sprintf("%d", envs[h_env->envc].count);
		    break;
		}
		if (INDENT_INCR >= 4)
		    Strcat_charp(num, ". ");
		else
		    Strcat_char(num, '.');
		push_spaces(obuf, 1, INDENT_INCR - num->length);
		push_str(obuf, num->length, num, PC_ASCII);
		if (INDENT_INCR >= 4)
		    set_space_to_prevchar(obuf->prevchar);
		break;
	    default:
		push_spaces(obuf, 1, INDENT_INCR);
		break;
	    }
	}
	else {
	    flushline(h_env, obuf, 0, 0, h_env->limit);
	}
	obuf->flag |= RB_IGNORE_P;
	return 1;
    case HTML_DT:
	CLOSE_A;
	if (h_env->envc == 0 ||
	    (h_env->envc_real < h_env->nenv &&
	     envs[h_env->envc].env != HTML_DL &&
	     envs[h_env->envc].env != HTML_DL_COMPACT)) {
	    PUSH_ENV(HTML_DL);
	}
	if (h_env->envc > 0) {
	    flushline(h_env, obuf,
		      envs[h_env->envc - 1].indent, 0, h_env->limit);
	}
	if (!(obuf->flag & RB_IN_DT)) {
	    HTMLlineproc1("", h_env);
	    obuf->flag |= RB_IN_DT;
	}
	obuf->flag |= RB_IGNORE_P;
	return 1;
    case HTML_DD:
	CLOSE_A;
	CLOSE_DT;
	if (h_env->envc == 0 ||
	    (h_env->envc_real < h_env->nenv &&
	     envs[h_env->envc].env != HTML_DL &&
	     envs[h_env->envc].env != HTML_DL_COMPACT)) {
	    PUSH_ENV(HTML_DL);
	}
	if (envs[h_env->envc].env == HTML_DL_COMPACT) {
	    if (obuf->pos > envs[h_env->envc].indent)
		flushline(h_env, obuf, envs[h_env->envc].indent, 0,
			  h_env->limit);
	    else
		push_spaces(obuf, 1, envs[h_env->envc].indent - obuf->pos);
	}
	else
	    flushline(h_env, obuf, envs[h_env->envc].indent, 0, h_env->limit);
	/* obuf->flag |= RB_IGNORE_P; */
	return 1;
    case HTML_TITLE:
	close_anchor(h_env, obuf);
	process_title(tag);
	obuf->flag |= RB_TITLE;
	obuf->end_tag = HTML_N_TITLE;
	return 1;
    case HTML_N_TITLE:
	if (!(obuf->flag & RB_TITLE))
	    return 1;
	obuf->flag &= ~RB_TITLE;
	obuf->end_tag = 0;
	tmp = process_n_title(tag);
	if (tmp)
	    HTMLlineproc1(tmp->ptr, h_env);
	return 1;
    case HTML_TITLE_ALT:
	if (parsedtag_get_value(tag, ATTR_TITLE, &p))
	    h_env->title = html_unquote(p);
	return 0;
    case HTML_FRAMESET:
	PUSH_ENV(cmd);
	push_charp(obuf, 9, "--FRAME--", PC_ASCII);
	flushline(h_env, obuf, envs[h_env->envc].indent, 0, h_env->limit);
	return 0;
    case HTML_N_FRAMESET:
	if (h_env->envc > 0) {
	    POP_ENV;
	    flushline(h_env, obuf, envs[h_env->envc].indent, 0, h_env->limit);
	}
	return 0;
    case HTML_NOFRAMES:
	CLOSE_A;
	flushline(h_env, obuf, envs[h_env->envc].indent, 0, h_env->limit);
	obuf->flag |= (RB_NOFRAMES | RB_IGNORE_P);
	/* istr = str; */
	return 1;
    case HTML_N_NOFRAMES:
	CLOSE_A;
	flushline(h_env, obuf, envs[h_env->envc].indent, 0, h_env->limit);
	obuf->flag &= ~RB_NOFRAMES;
	return 1;
    case HTML_FRAME:
	q = r = NULL;
	parsedtag_get_value(tag, ATTR_SRC, &q);
	parsedtag_get_value(tag, ATTR_NAME, &r);
	if (q) {
	    q = html_quote(q);
	    push_tag(obuf, Sprintf("",
				   cur_hseq++, q)->ptr, HTML_A);
	    if (r)
		q = html_quote(r);
	    push_charp(obuf, get_strwidth(q), q, PC_ASCII);
	    push_tag(obuf, "", HTML_N_A);
	}
	flushline(h_env, obuf, envs[h_env->envc].indent, 0, h_env->limit);
	return 0;
    case HTML_HR:
	close_anchor(h_env, obuf);
	tmp = process_hr(tag, h_env->limit, envs[h_env->envc].indent);
	HTMLlineproc1(tmp->ptr, h_env);
	set_space_to_prevchar(obuf->prevchar);
	return 1;
    case HTML_PRE:
	x = parsedtag_exists(tag, ATTR_FOR_TABLE);
	CLOSE_A;
	if (!(obuf->flag & RB_IGNORE_P)) {
	    flushline(h_env, obuf, envs[h_env->envc].indent, 0, h_env->limit);
	    if (!x)
		do_blankline(h_env, obuf, envs[h_env->envc].indent, 0,
			     h_env->limit);
	}
	else
	    fillline(obuf, envs[h_env->envc].indent);
	obuf->flag |= (RB_PRE | RB_IGNORE_P);
	/* istr = str; */
	return 1;
    case HTML_N_PRE:
	flushline(h_env, obuf, envs[h_env->envc].indent, 0, h_env->limit);
	if (!(obuf->flag & RB_IGNORE_P)) {
	    do_blankline(h_env, obuf, envs[h_env->envc].indent, 0,
			 h_env->limit);
	    obuf->flag |= RB_IGNORE_P;
	    h_env->blank_lines++;
	}
	obuf->flag &= ~RB_PRE;
	close_anchor(h_env, obuf);
	return 1;
    case HTML_PRE_INT:
	i = obuf->line->length;
	append_tags(obuf);
	if (!(obuf->flag & RB_SPECIAL)) {
	    set_breakpoint(obuf, obuf->line->length - i);
	}
	obuf->flag |= RB_PRE_INT;
	return 0;
    case HTML_N_PRE_INT:
	push_tag(obuf, "</pre_int>", HTML_N_PRE_INT);
	obuf->flag &= ~RB_PRE_INT;
	if (!(obuf->flag & RB_SPECIAL) && obuf->pos > obuf->bp.pos) {
	    set_prevchar(obuf->prevchar, "", 0);
	    obuf->prev_ctype = PC_CTRL;
	}
	return 1;
    case HTML_NOBR:
	obuf->flag |= RB_NOBR;
	obuf->nobr_level++;
	return 0;
    case HTML_N_NOBR:
	if (obuf->nobr_level > 0)
	    obuf->nobr_level--;
	if (obuf->nobr_level == 0)
	    obuf->flag &= ~RB_NOBR;
	return 0;
    case HTML_PRE_PLAIN:
	CLOSE_A;
	if (!(obuf->flag & RB_IGNORE_P)) {
	    flushline(h_env, obuf, envs[h_env->envc].indent, 0, h_env->limit);
	    do_blankline(h_env, obuf, envs[h_env->envc].indent, 0,
			 h_env->limit);
	}
	obuf->flag |= (RB_PRE | RB_IGNORE_P);
	return 1;
    case HTML_N_PRE_PLAIN:
	CLOSE_A;
	if (!(obuf->flag & RB_IGNORE_P)) {
	    flushline(h_env, obuf, envs[h_env->envc].indent, 0, h_env->limit);
	    do_blankline(h_env, obuf, envs[h_env->envc].indent, 0,
			 h_env->limit);
	    obuf->flag |= RB_IGNORE_P;
	}
	obuf->flag &= ~RB_PRE;
	return 1;
    case HTML_LISTING:
    case HTML_XMP:
    case HTML_PLAINTEXT:
	CLOSE_A;
	if (!(obuf->flag & RB_IGNORE_P)) {
	    flushline(h_env, obuf, envs[h_env->envc].indent, 0, h_env->limit);
	    do_blankline(h_env, obuf, envs[h_env->envc].indent, 0,
			 h_env->limit);
	}
	obuf->flag |= (RB_PLAIN | RB_IGNORE_P);
	switch (cmd) {
	case HTML_LISTING:
	    obuf->end_tag = HTML_N_LISTING;
	    break;
	case HTML_XMP:
	    obuf->end_tag = HTML_N_XMP;
	    break;
	case HTML_PLAINTEXT:
	    obuf->end_tag = MAX_HTMLTAG;
	    break;
	}
	return 1;
    case HTML_N_LISTING:
    case HTML_N_XMP:
	CLOSE_A;
	if (!(obuf->flag & RB_IGNORE_P)) {
	    flushline(h_env, obuf, envs[h_env->envc].indent, 0, h_env->limit);
	    do_blankline(h_env, obuf, envs[h_env->envc].indent, 0,
			 h_env->limit);
	    obuf->flag |= RB_IGNORE_P;
	}
	obuf->flag &= ~RB_PLAIN;
	obuf->end_tag = 0;
	return 1;
    case HTML_SCRIPT:
	obuf->flag |= RB_SCRIPT;
	obuf->end_tag = HTML_N_SCRIPT;
	return 1;
    case HTML_STYLE:
	obuf->flag |= RB_STYLE;
	obuf->end_tag = HTML_N_STYLE;
	return 1;
    case HTML_N_SCRIPT:
	obuf->flag &= ~RB_SCRIPT;
	obuf->end_tag = 0;
	return 1;
    case HTML_N_STYLE:
	obuf->flag &= ~RB_STYLE;
	obuf->end_tag = 0;
	return 1;
    case HTML_A:
	if (obuf->anchor.url)
	    close_anchor(h_env, obuf);

	hseq = 0;

	if (parsedtag_get_value(tag, ATTR_HREF, &p))
	    obuf->anchor.url = Strnew_charp(p)->ptr;
	if (parsedtag_get_value(tag, ATTR_TARGET, &p))
	    obuf->anchor.target = Strnew_charp(p)->ptr;
	if (parsedtag_get_value(tag, ATTR_REFERER, &p))
	    obuf->anchor.referer = Strnew_charp(p)->ptr;
	if (parsedtag_get_value(tag, ATTR_TITLE, &p))
	    obuf->anchor.title = Strnew_charp(p)->ptr;
	if (parsedtag_get_value(tag, ATTR_ACCESSKEY, &p))
	    obuf->anchor.accesskey = (unsigned char)*p;
	if (parsedtag_get_value(tag, ATTR_HSEQ, &hseq))
	    obuf->anchor.hseq = hseq;

	if (hseq == 0 && obuf->anchor.url) {
	    obuf->anchor.hseq = cur_hseq;
	    tmp = process_anchor(tag, h_env->tagbuf->ptr);
	    push_tag(obuf, tmp->ptr, HTML_A);
	    if (displayLinkNumber)
		HTMLlineproc1(getLinkNumberStr(-1)->ptr, h_env);
	    return 1;
	}
	return 0;
    case HTML_N_A:
	close_anchor(h_env, obuf);
	return 1;
    case HTML_IMG:
	tmp = process_img(tag, h_env->limit);
	HTMLlineproc1(tmp->ptr, h_env);
	return 1;
    case HTML_IMG_ALT:
	if (parsedtag_get_value(tag, ATTR_SRC, &p))
	    obuf->img_alt = Strnew_charp(p);
#ifdef USE_IMAGE
	i = 0;
	if (parsedtag_get_value(tag, ATTR_TOP_MARGIN, &i)) {
	    if (i > obuf->top_margin)
		obuf->top_margin = i;
	}
	i = 0;
	if (parsedtag_get_value(tag, ATTR_BOTTOM_MARGIN, &i)) {
	    if (i > obuf->bottom_margin)
		obuf->bottom_margin = i;
	}
#endif
	return 0;
    case HTML_N_IMG_ALT:
	if (obuf->img_alt) {
	    if (!close_effect0(obuf, HTML_IMG_ALT))
		push_tag(obuf, "</img_alt>", HTML_N_IMG_ALT);
	    obuf->img_alt = NULL;
	}
	return 1;
    case HTML_INPUT_ALT:
	i = 0;
	if (parsedtag_get_value(tag, ATTR_TOP_MARGIN, &i)) {
	    if (i > obuf->top_margin)
		obuf->top_margin = i;
	}
	i = 0;
	if (parsedtag_get_value(tag, ATTR_BOTTOM_MARGIN, &i)) {
	    if (i > obuf->bottom_margin)
		obuf->bottom_margin = i;
	}
	if (parsedtag_get_value(tag, ATTR_HSEQ, &hseq)) {
	    obuf->input_alt.hseq = hseq;
	}
	if (parsedtag_get_value(tag, ATTR_FID, &i)) {
	    obuf->input_alt.fid = i;
	}
	if (parsedtag_get_value(tag, ATTR_TYPE, &p)) {
	    obuf->input_alt.type = Strnew_charp(p);
	}
	if (parsedtag_get_value(tag, ATTR_VALUE, &p)) {
	    obuf->input_alt.value = Strnew_charp(p);
	}
	if (parsedtag_get_value(tag, ATTR_NAME, &p)) {
	    obuf->input_alt.name = Strnew_charp(p);
	}
	obuf->input_alt.in = 1;
	return 0;
    case HTML_N_INPUT_ALT:
	if (obuf->input_alt.in) {
	    if (!close_effect0(obuf, HTML_INPUT_ALT))
		push_tag(obuf, "</input_alt>", HTML_N_INPUT_ALT);
	    obuf->input_alt.hseq = 0;
	    obuf->input_alt.fid = -1;
	    obuf->input_alt.in = 0;
	    obuf->input_alt.type = NULL;
	    obuf->input_alt.name = NULL;
	    obuf->input_alt.value = NULL;
	}
	return 1;
    case HTML_TABLE:
	close_anchor(h_env, obuf);
	obuf->table_level++;
	if (obuf->table_level >= MAX_TABLE)
	    break;
	w = BORDER_NONE;
	/* x: cellspacing, y: cellpadding */
	x = 2;
	y = 1;
	z = 0;
	width = 0;
	if (parsedtag_exists(tag, ATTR_BORDER)) {
	    if (parsedtag_get_value(tag, ATTR_BORDER, &w)) {
		if (w > 2)
		    w = BORDER_THICK;
		else if (w < 0) {	/* weird */
		    w = BORDER_THIN;
		}
	    }
	    else
		w = BORDER_THIN;
	}
	if (DisplayBorders && w == BORDER_NONE)
	    w = BORDER_THIN;
	if (parsedtag_get_value(tag, ATTR_WIDTH, &i)) {
	    if (obuf->table_level == 0)
		width = REAL_WIDTH(i, h_env->limit - envs[h_env->envc].indent);
	    else
		width = RELATIVE_WIDTH(i);
	}
	if (parsedtag_exists(tag, ATTR_HBORDER))
	    w = BORDER_NOWIN;
#define MAX_CELLSPACING 1000
#define MAX_CELLPADDING 1000
#define MAX_VSPACE 1000
	parsedtag_get_value(tag, ATTR_CELLSPACING, &x);
	parsedtag_get_value(tag, ATTR_CELLPADDING, &y);
	parsedtag_get_value(tag, ATTR_VSPACE, &z);
	if (x < 0)
	    x = 0;
	if (y < 0)
	    y = 0;
	if (z < 0)
	    z = 0;
	if (x > MAX_CELLSPACING)
	    x = MAX_CELLSPACING;
	if (y > MAX_CELLPADDING)
	    y = MAX_CELLPADDING;
	if (z > MAX_VSPACE)
	    z = MAX_VSPACE;
#ifdef ID_EXT
	parsedtag_get_value(tag, ATTR_ID, &id);
#endif				/* ID_EXT */
	tables[obuf->table_level] = begin_table(w, x, y, z);
#ifdef ID_EXT
	if (id != NULL)
	    tables[obuf->table_level]->id = Strnew_charp(id);
#endif				/* ID_EXT */
	table_mode[obuf->table_level].pre_mode = 0;
	table_mode[obuf->table_level].indent_level = 0;
	table_mode[obuf->table_level].nobr_level = 0;
	table_mode[obuf->table_level].caption = 0;
	table_mode[obuf->table_level].end_tag = 0;	/* HTML_UNKNOWN */
#ifndef TABLE_EXPAND
	tables[obuf->table_level]->total_width = width;
#else
	tables[obuf->table_level]->real_width = width;
	tables[obuf->table_level]->total_width = 0;
#endif
	return 1;
    case HTML_N_TABLE:
	/* should be processed in HTMLlineproc() */
	return 1;
    case HTML_CENTER:
	CLOSE_A;
	if (!(obuf->flag & (RB_PREMODE | RB_IGNORE_P)))
	    flushline(h_env, obuf, envs[h_env->envc].indent, 0, h_env->limit);
	RB_SAVE_FLAG(obuf);
	RB_SET_ALIGN(obuf, RB_CENTER);
	return 1;
    case HTML_N_CENTER:
	CLOSE_A;
	if (!(obuf->flag & RB_PREMODE))
	    flushline(h_env, obuf, envs[h_env->envc].indent, 0, h_env->limit);
	RB_RESTORE_FLAG(obuf);
	return 1;
    case HTML_DIV:
	CLOSE_A;
	if (!(obuf->flag & RB_IGNORE_P))
	    flushline(h_env, obuf, envs[h_env->envc].indent, 0, h_env->limit);
	set_alignment(obuf, tag);
	return 1;
    case HTML_N_DIV:
	CLOSE_A;
	flushline(h_env, obuf, envs[h_env->envc].indent, 0, h_env->limit);
	RB_RESTORE_FLAG(obuf);
	return 1;
    case HTML_DIV_INT:
	CLOSE_P;
	if (!(obuf->flag & RB_IGNORE_P))
	    flushline(h_env, obuf, envs[h_env->envc].indent, 0, h_env->limit);
	set_alignment(obuf, tag);
	return 1;
    case HTML_N_DIV_INT:
	CLOSE_P;
	flushline(h_env, obuf, envs[h_env->envc].indent, 0, h_env->limit);
	RB_RESTORE_FLAG(obuf);
	return 1;
    case HTML_FORM:
	CLOSE_A;
	if (!(obuf->flag & RB_IGNORE_P))
	    flushline(h_env, obuf, envs[h_env->envc].indent, 0, h_env->limit);
	tmp = process_form(tag);
	if (tmp)
	    HTMLlineproc1(tmp->ptr, h_env);
	return 1;
    case HTML_N_FORM:
	CLOSE_A;
	flushline(h_env, obuf, envs[h_env->envc].indent, 0, h_env->limit);
	obuf->flag |= RB_IGNORE_P;
	process_n_form();
	return 1;
    case HTML_INPUT:
	close_anchor(h_env, obuf);
	tmp = process_input(tag);
       if (tmp)
           HTMLlineproc1(tmp->ptr, h_env);
       return 1;
    case HTML_BUTTON:
       tmp = process_button(tag);
       if (tmp)
           HTMLlineproc1(tmp->ptr, h_env);
       return 1;
    case HTML_N_BUTTON:
       tmp = process_n_button();
	if (tmp)
	    HTMLlineproc1(tmp->ptr, h_env);
	return 1;
    case HTML_SELECT:
	close_anchor(h_env, obuf);
	tmp = process_select(tag);
	if (tmp)
	    HTMLlineproc1(tmp->ptr, h_env);
	obuf->flag |= RB_INSELECT;
	obuf->end_tag = HTML_N_SELECT;
	return 1;
    case HTML_N_SELECT:
	obuf->flag &= ~RB_INSELECT;
	obuf->end_tag = 0;
	tmp = process_n_select();
	if (tmp)
	    HTMLlineproc1(tmp->ptr, h_env);
	return 1;
    case HTML_OPTION:
	/* nothing */
	return 1;
    case HTML_TEXTAREA:
	close_anchor(h_env, obuf);
	tmp = process_textarea(tag, h_env->limit);
	if (tmp)
	    HTMLlineproc1(tmp->ptr, h_env);
	obuf->flag |= RB_INTXTA;
	obuf->end_tag = HTML_N_TEXTAREA;
	return 1;
    case HTML_N_TEXTAREA:
	obuf->flag &= ~RB_INTXTA;
	obuf->end_tag = 0;
	tmp = process_n_textarea();
	if (tmp)
	    HTMLlineproc1(tmp->ptr, h_env);
	return 1;
    case HTML_ISINDEX:
	p = "";
	q = "!CURRENT_URL!";
	parsedtag_get_value(tag, ATTR_PROMPT, &p);
	parsedtag_get_value(tag, ATTR_ACTION, &q);
	tmp = Strnew_m_charp("<form method=get action=\"",
			     html_quote(q),
			     "\">",
			     html_quote(p),
			     "<input type=text name=\"\" accept></form>",
			     NULL);
	HTMLlineproc1(tmp->ptr, h_env);
	return 1;
    case HTML_META:
	p = q = r = NULL;
	parsedtag_get_value(tag, ATTR_HTTP_EQUIV, &p);
	parsedtag_get_value(tag, ATTR_CONTENT, &q);
#ifdef USE_M17N
	parsedtag_get_value(tag, ATTR_CHARSET, &r);
	if (r) {
	    /* <meta charset=""> */
	    SKIP_BLANKS(r);
	    meta_charset = wc_guess_charset(r, 0);
	}
	else
	if (p && q && !strcasecmp(p, "Content-Type") &&
	    (q = strcasestr(q, "charset")) != NULL) {
	    q += 7;
	    SKIP_BLANKS(q);
	    if (*q == '=') {
		q++;
		SKIP_BLANKS(q);
		meta_charset = wc_guess_charset(q, 0);
	    }
	}
	else
#endif
	if (p && q && !strcasecmp(p, "refresh")) {
	    int refresh_interval;
	    tmp = NULL;
	    refresh_interval = getMetaRefreshParam(q, &tmp);
	    if (tmp) {
		q = html_quote(tmp->ptr);
		tmp = Sprintf("Refresh (%d sec) %s",
			      refresh_interval, q, q);
	    }
	    else if (refresh_interval > 0)
		tmp = Sprintf("Refresh (%d sec)", refresh_interval);
	    if (tmp) {
		HTMLlineproc1(tmp->ptr, h_env);
		do_blankline(h_env, obuf, envs[h_env->envc].indent, 0,
			     h_env->limit);
		if (!is_redisplay &&
		    !((obuf->flag & RB_NOFRAMES) && RenderFrame)) {
		    tag->need_reconstruct = TRUE;
		    return 0;
		}
	    }
	}
	return 1;
    case HTML_BASE:
#if defined(USE_M17N) || defined(USE_IMAGE)
	p = NULL;
	if (parsedtag_get_value(tag, ATTR_HREF, &p)) {
	    cur_baseURL = New(ParsedURL);
	    parseURL(p, cur_baseURL, NULL);
	}
#endif
    case HTML_MAP:
    case HTML_N_MAP:
    case HTML_AREA:
	return 0;
    case HTML_DEL:
	switch (displayInsDel) {
	case DISPLAY_INS_DEL_SIMPLE:
	    obuf->flag |= RB_DEL;
	    break;
	case DISPLAY_INS_DEL_NORMAL:
	    HTMLlineproc1("[DEL:", h_env);
	    break;
	case DISPLAY_INS_DEL_FONTIFY:
	    obuf->in_strike++;
	    if (obuf->in_strike == 1) {
		push_tag(obuf, "<s>", HTML_S);
	    }
	    break;
	}
	return 1;
    case HTML_N_DEL:
	switch (displayInsDel) {
	case DISPLAY_INS_DEL_SIMPLE:
	    obuf->flag &= ~RB_DEL;
	    break;
	case DISPLAY_INS_DEL_NORMAL:
	    HTMLlineproc1(":DEL]", h_env);
	case DISPLAY_INS_DEL_FONTIFY:
	    if (obuf->in_strike == 0)
		return 1;
	    if (obuf->in_strike == 1 && close_effect0(obuf, HTML_S))
		obuf->in_strike = 0;
	    if (obuf->in_strike > 0) {
		obuf->in_strike--;
		if (obuf->in_strike == 0) {
		    push_tag(obuf, "</s>", HTML_N_S);
		}
	    }
	    break;
	}
	return 1;
    case HTML_S:
	switch (displayInsDel) {
	case DISPLAY_INS_DEL_SIMPLE:
	    obuf->flag |= RB_S;
	    break;
	case DISPLAY_INS_DEL_NORMAL:
	    HTMLlineproc1("[S:", h_env);
	    break;
	case DISPLAY_INS_DEL_FONTIFY:
	    obuf->in_strike++;
	    if (obuf->in_strike == 1) {
		push_tag(obuf, "<s>", HTML_S);
	    }
	    break;
	}
	return 1;
    case HTML_N_S:
	switch (displayInsDel) {
	case DISPLAY_INS_DEL_SIMPLE:
	    obuf->flag &= ~RB_S;
	    break;
	case DISPLAY_INS_DEL_NORMAL:
	    HTMLlineproc1(":S]", h_env);
	    break;
	case DISPLAY_INS_DEL_FONTIFY:
	    if (obuf->in_strike == 0)
		return 1;
	    if (obuf->in_strike == 1 && close_effect0(obuf, HTML_S))
		obuf->in_strike = 0;
	    if (obuf->in_strike > 0) {
		obuf->in_strike--;
		if (obuf->in_strike == 0) {
		    push_tag(obuf, "</s>", HTML_N_S);
		}
	    }
	}
	return 1;
    case HTML_INS:
	switch (displayInsDel) {
	case DISPLAY_INS_DEL_SIMPLE:
	    break;
	case DISPLAY_INS_DEL_NORMAL:
	    HTMLlineproc1("[INS:", h_env);
	    break;
	case DISPLAY_INS_DEL_FONTIFY:
	    obuf->in_ins++;
	    if (obuf->in_ins == 1) {
		push_tag(obuf, "", HTML_INS);
	    }
	    break;
	}
	return 1;
    case HTML_N_INS:
	switch (displayInsDel) {
	case DISPLAY_INS_DEL_SIMPLE:
	    break;
	case DISPLAY_INS_DEL_NORMAL:
	    HTMLlineproc1(":INS]", h_env);
	    break;
	case DISPLAY_INS_DEL_FONTIFY:
	    if (obuf->in_ins == 0)
		return 1;
	    if (obuf->in_ins == 1 && close_effect0(obuf, HTML_INS))
		obuf->in_ins = 0;
	    if (obuf->in_ins > 0) {
		obuf->in_ins--;
		if (obuf->in_ins == 0) {
		    push_tag(obuf, "", HTML_N_INS);
		}
	    }
	    break;
	}
	return 1;
    case HTML_SUP:
	if (!(obuf->flag & (RB_DEL | RB_S)))
	    HTMLlineproc1("^", h_env);
	return 1;
    case HTML_N_SUP:
	return 1;
    case HTML_SUB:
	if (!(obuf->flag & (RB_DEL | RB_S)))
	    HTMLlineproc1("[", h_env);
	return 1;
    case HTML_N_SUB:
	if (!(obuf->flag & (RB_DEL | RB_S)))
	    HTMLlineproc1("]", h_env);
	return 1;
    case HTML_FONT:
    case HTML_N_FONT:
    case HTML_NOP:
	return 1;
    case HTML_BGSOUND:
	if (view_unseenobject) {
	    if (parsedtag_get_value(tag, ATTR_SRC, &p)) {
		Str s;
		q = html_quote(p);
		s = Sprintf("bgsound(%s)", q, q);
		HTMLlineproc1(s->ptr, h_env);
	    }
	}
	return 1;
    case HTML_EMBED:
	if (view_unseenobject) {
	    if (parsedtag_get_value(tag, ATTR_SRC, &p)) {
		Str s;
		q = html_quote(p);
		s = Sprintf("embed(%s)", q, q);
		HTMLlineproc1(s->ptr, h_env);
	    }
	}
	return 1;
    case HTML_APPLET:
	if (view_unseenobject) {
	    if (parsedtag_get_value(tag, ATTR_ARCHIVE, &p)) {
		Str s;
		q = html_quote(p);
		s = Sprintf("applet archive(%s)", q, q);
		HTMLlineproc1(s->ptr, h_env);
	    }
	}
	return 1;
    case HTML_BODY:
	if (view_unseenobject) {
	    if (parsedtag_get_value(tag, ATTR_BACKGROUND, &p)) {
		Str s;
		q = html_quote(p);
		s = Sprintf("
", q, q);
		HTMLlineproc1(s->ptr, h_env);
	    }
	}
    case HTML_N_HEAD:
	if (obuf->flag & RB_TITLE)
	    HTMLlineproc1("</title>", h_env);
    case HTML_HEAD:
    case HTML_N_BODY:
	return 1;
    default:
	/* obuf->prevchar = '\0'; */
	return 0;
    }
    /* not reached */
    return 0;
}

An issue was discovered in the Tatsuya Kinoshita w3m fork before 0.5.3-33. Infinite recursion vulnerability in w3m allows remote attackers to cause a denial of service via a crafted HTML page.