Blame common/expr.c

Packit 6bd9ab
/*
Packit 6bd9ab
   expr.c - limited shell-like expression parsing functions
Packit 6bd9ab
   This file is part of the nss-pam-ldapd library.
Packit 6bd9ab
Packit 6bd9ab
   Copyright (C) 2009-2016 Arthur de Jong
Packit 6bd9ab
   Copyright (c) 2012 Thorsten Glaser <t.glaser@tarent.de>
Packit 6bd9ab
   Copyright (c) 2016 Giovanni Mascellani <gio@debian.org>
Packit 6bd9ab
Packit 6bd9ab
   This library is free software; you can redistribute it and/or
Packit 6bd9ab
   modify it under the terms of the GNU Lesser General Public
Packit 6bd9ab
   License as published by the Free Software Foundation; either
Packit 6bd9ab
   version 2.1 of the License, or (at your option) any later version.
Packit 6bd9ab
Packit 6bd9ab
   This library is distributed in the hope that it will be useful,
Packit 6bd9ab
   but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 6bd9ab
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Packit 6bd9ab
   Lesser General Public License for more details.
Packit 6bd9ab
Packit 6bd9ab
   You should have received a copy of the GNU Lesser General Public
Packit 6bd9ab
   License along with this library; if not, write to the Free Software
Packit 6bd9ab
   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
Packit 6bd9ab
   02110-1301 USA
Packit 6bd9ab
*/
Packit 6bd9ab
Packit 6bd9ab
#include "config.h"
Packit 6bd9ab
Packit 6bd9ab
#include <stdlib.h>
Packit 6bd9ab
#include <string.h>
Packit 6bd9ab
#include <stdio.h>
Packit 6bd9ab
#include <errno.h>
Packit 6bd9ab
Packit 6bd9ab
#include "expr.h"
Packit 6bd9ab
#include "compat/attrs.h"
Packit 6bd9ab
Packit 6bd9ab
/* the maximum length of a variable name */
Packit 6bd9ab
#define MAXVARLENGTH 30
Packit 6bd9ab
Packit 6bd9ab
static inline int my_isalpha(const char c)
Packit 6bd9ab
{
Packit 6bd9ab
  return ((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z'));
Packit 6bd9ab
}
Packit 6bd9ab
Packit 6bd9ab
static inline int my_isdigit(const char c)
Packit 6bd9ab
{
Packit 6bd9ab
  return (c >= '0') && (c <= '9');
Packit 6bd9ab
}
Packit 6bd9ab
Packit 6bd9ab
static inline int my_isalphanum(const char c)
Packit 6bd9ab
{
Packit 6bd9ab
  return my_isalpha(c) || ((c >= '0') && (c <= '9'));
Packit 6bd9ab
}
Packit 6bd9ab
Packit 6bd9ab
/* return the part of the string that is a valid name */
Packit 6bd9ab
MUST_USE static const char *parse_name(const char *str, int *ptr,
Packit 6bd9ab
                                       char *buffer, size_t buflen)
Packit 6bd9ab
{
Packit 6bd9ab
  int i = 0;
Packit 6bd9ab
  /* clear the buffer */
Packit 6bd9ab
  buffer[i] = '\0';
Packit 6bd9ab
  /* look for an alpha + alphanumeric* string */
Packit 6bd9ab
  if (!my_isalpha(str[*ptr]))
Packit 6bd9ab
    return NULL;
Packit 6bd9ab
  while (my_isalphanum(str[*ptr]) || (str[*ptr] == ';'))
Packit 6bd9ab
  {
Packit 6bd9ab
    if ((size_t)i >= buflen)
Packit 6bd9ab
      return NULL;
Packit 6bd9ab
    buffer[i++] = str[(*ptr)++];
Packit 6bd9ab
  }
Packit 6bd9ab
  /* NULL-terminate the string */
Packit 6bd9ab
  if ((size_t)i >= buflen)
Packit 6bd9ab
    return NULL;
Packit 6bd9ab
  buffer[i++] = '\0';
Packit 6bd9ab
  return buffer;
Packit 6bd9ab
}
Packit 6bd9ab
Packit 6bd9ab
/* dummy expander function to always return an empty string */
Packit 6bd9ab
static const char *empty_expander(const char UNUSED(*name),
Packit 6bd9ab
                                  void UNUSED(*expander_arg))
Packit 6bd9ab
{
Packit 6bd9ab
  return "";
Packit 6bd9ab
}
Packit 6bd9ab
Packit 6bd9ab
/* definition of the parse functions (they call eachother) */
Packit 6bd9ab
MUST_USE static const char *parse_dollar_expression(
Packit 6bd9ab
              const char *str, int *ptr, char *buffer, size_t buflen,
Packit 6bd9ab
              expr_expander_func expander, void *expander_arg);
Packit 6bd9ab
MUST_USE static const char *parse_expression(
Packit 6bd9ab
              const char *str, int *ptr, int endat, char *buffer, size_t buflen,
Packit 6bd9ab
              expr_expander_func expander, void *expander_arg);
Packit 6bd9ab
Packit 6bd9ab
/* handle ${attr:-word} expressions */
Packit 6bd9ab
MUST_USE static const char *parse_dollar_default(
Packit 6bd9ab
              const char *str, int *ptr, char *buffer, size_t buflen,
Packit 6bd9ab
              expr_expander_func expander, void *expander_arg,
Packit 6bd9ab
              const char *varvalue)
Packit 6bd9ab
{
Packit 6bd9ab
  if ((varvalue != NULL) && (*varvalue != '\0'))
Packit 6bd9ab
  {
Packit 6bd9ab
    /* value is set, skip rest of expression and use value */
Packit 6bd9ab
    if (parse_expression(str, ptr, '}', buffer, buflen, empty_expander, NULL) == NULL)
Packit 6bd9ab
      return NULL;
Packit 6bd9ab
    if (strlen(varvalue) >= buflen)
Packit 6bd9ab
      return NULL;
Packit 6bd9ab
    strcpy(buffer, varvalue);
Packit 6bd9ab
  }
Packit 6bd9ab
  else
Packit 6bd9ab
  {
Packit 6bd9ab
    /* value is not set, evaluate rest of expression */
Packit 6bd9ab
    if (parse_expression(str, ptr, '}', buffer, buflen, expander, expander_arg) == NULL)
Packit 6bd9ab
      return NULL;
Packit 6bd9ab
  }
Packit 6bd9ab
  return buffer;
Packit 6bd9ab
}
Packit 6bd9ab
Packit 6bd9ab
/* handle ${attr:+word} expressions */
Packit 6bd9ab
MUST_USE static const char *parse_dollar_alternative(
Packit 6bd9ab
              const char *str, int *ptr, char *buffer, size_t buflen,
Packit 6bd9ab
              expr_expander_func expander, void *expander_arg,
Packit 6bd9ab
              const char *varvalue)
Packit 6bd9ab
{
Packit 6bd9ab
  if ((varvalue != NULL) && (*varvalue != '\0'))
Packit 6bd9ab
  {
Packit 6bd9ab
    /* value is set, evaluate rest of expression */
Packit 6bd9ab
    if (parse_expression(str, ptr, '}', buffer, buflen, expander, expander_arg) == NULL)
Packit 6bd9ab
      return NULL;
Packit 6bd9ab
  }
Packit 6bd9ab
  else
Packit 6bd9ab
  {
Packit 6bd9ab
    /* value is not set, skip rest of expression and blank */
Packit 6bd9ab
    if (parse_expression(str, ptr, '}', buffer, buflen, empty_expander, NULL) == NULL)
Packit 6bd9ab
      return NULL;
Packit 6bd9ab
    buffer[0] = '\0';
Packit 6bd9ab
  }
Packit 6bd9ab
  return buffer;
Packit 6bd9ab
}
Packit 6bd9ab
Packit 6bd9ab
/* handle ${attr:offset:length} expressions */
Packit 6bd9ab
MUST_USE static const char *parse_dollar_substring(
Packit 6bd9ab
              const char *str, int *ptr, char *buffer, size_t buflen,
Packit 6bd9ab
              const char *varvalue)
Packit 6bd9ab
{
Packit 6bd9ab
  char *tmp;
Packit 6bd9ab
  unsigned long int offset, length;
Packit 6bd9ab
  size_t varlen;
Packit 6bd9ab
  /* parse input */
Packit 6bd9ab
  tmp = (char *)str + *ptr;
Packit 6bd9ab
  if (!my_isdigit(*tmp))
Packit 6bd9ab
    return NULL;
Packit 6bd9ab
  errno = 0;
Packit 6bd9ab
  offset = strtoul(tmp, &tmp, 10);
Packit 6bd9ab
  if ((*tmp != ':') || (errno != 0))
Packit 6bd9ab
    return NULL;
Packit 6bd9ab
  tmp += 1;
Packit 6bd9ab
  errno = 0;
Packit 6bd9ab
  length = strtoul(tmp, &tmp, 10);
Packit 6bd9ab
  if ((*tmp != '}') || (errno != 0))
Packit 6bd9ab
    return NULL;
Packit 6bd9ab
  /* don't skip closing '}' here, because it will be skipped later */
Packit 6bd9ab
  *ptr += tmp - (str + *ptr);
Packit 6bd9ab
  varlen = strlen(varvalue);
Packit 6bd9ab
  if (offset > varlen)
Packit 6bd9ab
    offset = varlen;
Packit 6bd9ab
  if (offset + length > varlen)
Packit 6bd9ab
    length = varlen - offset;
Packit 6bd9ab
  if (length >= buflen)
Packit 6bd9ab
    return NULL;
Packit 6bd9ab
  /* everything's ok, copy data; we use memcpy instead of strncpy
Packit 6bd9ab
     because we already know the exact lenght in play */
Packit 6bd9ab
  memcpy(buffer, varvalue + offset, length);
Packit 6bd9ab
  buffer[length] = '\0';
Packit 6bd9ab
  return buffer;
Packit 6bd9ab
}
Packit 6bd9ab
Packit 6bd9ab
/* handle ${attr#word} expressions */
Packit 6bd9ab
MUST_USE static const char *parse_dollar_match(
Packit 6bd9ab
              const char *str, int *ptr, char *buffer, size_t buflen,
Packit 6bd9ab
              const char *varvalue)
Packit 6bd9ab
{
Packit 6bd9ab
  char c;
Packit 6bd9ab
  const char *cp, *vp;
Packit 6bd9ab
  int ismatch;
Packit 6bd9ab
  size_t vallen;
Packit 6bd9ab
  cp = str + *ptr;
Packit 6bd9ab
  vp = varvalue;
Packit 6bd9ab
  ismatch = 1;
Packit 6bd9ab
  while (((c = *cp++) != '\0') && (c != '}'))
Packit 6bd9ab
  {
Packit 6bd9ab
    if (ismatch && (*vp =='\0'))
Packit 6bd9ab
      ismatch = 0; /* varvalue shorter than trim string */
Packit 6bd9ab
    if (c == '?')
Packit 6bd9ab
    {
Packit 6bd9ab
      /* match any one character */
Packit 6bd9ab
      vp++;
Packit 6bd9ab
      continue;
Packit 6bd9ab
    }
Packit 6bd9ab
    if (c == '\\')
Packit 6bd9ab
    {
Packit 6bd9ab
      if ((c = *cp++) == '\0')
Packit 6bd9ab
        return NULL; /* end of input: syntax error */
Packit 6bd9ab
      /* escape the next character c */
Packit 6bd9ab
    }
Packit 6bd9ab
    if (ismatch && (*vp != c))
Packit 6bd9ab
      ismatch = 0; /* they differ */
Packit 6bd9ab
    vp++;
Packit 6bd9ab
  }
Packit 6bd9ab
  if (c == '\0')
Packit 6bd9ab
    return NULL; /* end of input: syntax error */
Packit 6bd9ab
  /* at this point, cp points to after the closing } */
Packit 6bd9ab
  (*ptr) = cp - str - 1;
Packit 6bd9ab
  /* if ismatch, vp points to the beginning of the
Packit 6bd9ab
     data after trimming, otherwise vp is invalid */
Packit 6bd9ab
  if (!ismatch)
Packit 6bd9ab
    vp = varvalue;
Packit 6bd9ab
  /* now copy the (trimmed or not) value to the buffer */
Packit 6bd9ab
  if ((vallen = strlen(vp) + 1) > buflen)
Packit 6bd9ab
    return NULL;
Packit 6bd9ab
  memcpy(buffer, vp, vallen);
Packit 6bd9ab
  return buffer;
Packit 6bd9ab
}
Packit 6bd9ab
Packit 6bd9ab
MUST_USE static const char *parse_dollar_expression(
Packit 6bd9ab
              const char *str, int *ptr, char *buffer, size_t buflen,
Packit 6bd9ab
              expr_expander_func expander, void *expander_arg)
Packit 6bd9ab
{
Packit 6bd9ab
  char varname[MAXVARLENGTH];
Packit 6bd9ab
  const char *varvalue;
Packit 6bd9ab
  if ((buflen <= 0) || (buffer == NULL) || (str == NULL) || (ptr == NULL))
Packit 6bd9ab
    return NULL;
Packit 6bd9ab
  if (str[*ptr] == '{')
Packit 6bd9ab
  {
Packit 6bd9ab
    (*ptr)++;
Packit 6bd9ab
    /* the first part is always a variable name */
Packit 6bd9ab
    if (parse_name(str, ptr, varname, sizeof(varname)) == NULL)
Packit 6bd9ab
      return NULL;
Packit 6bd9ab
    varvalue = expander(varname, expander_arg);
Packit 6bd9ab
    if (varvalue == NULL)
Packit 6bd9ab
      varvalue = "";
Packit 6bd9ab
    if (str[*ptr] == '}')
Packit 6bd9ab
    {
Packit 6bd9ab
      /* simple substitute */
Packit 6bd9ab
      if (strlen(varvalue) >= buflen)
Packit 6bd9ab
        return NULL;
Packit 6bd9ab
      strcpy(buffer, varvalue);
Packit 6bd9ab
    }
Packit 6bd9ab
    else if (strncmp(str + *ptr, ":-", 2) == 0)
Packit 6bd9ab
    {
Packit 6bd9ab
      /* if variable is not set or empty, substitute remainder */
Packit 6bd9ab
      (*ptr) += 2;
Packit 6bd9ab
      if (parse_dollar_default(str, ptr, buffer, buflen, expander, expander_arg, varvalue) == NULL)
Packit 6bd9ab
        return NULL;
Packit 6bd9ab
    }
Packit 6bd9ab
    else if (strncmp(str + *ptr, ":+", 2) == 0)
Packit 6bd9ab
    {
Packit 6bd9ab
      /* if variable is set, substitute remainder */
Packit 6bd9ab
      (*ptr) += 2;
Packit 6bd9ab
      if (parse_dollar_alternative(str, ptr, buffer, buflen, expander, expander_arg, varvalue) == NULL)
Packit 6bd9ab
        return NULL;
Packit 6bd9ab
    }
Packit 6bd9ab
    else if (str[*ptr] == ':')
Packit 6bd9ab
    {
Packit 6bd9ab
      /* substitute substring of variable */
Packit 6bd9ab
      (*ptr) += 1;
Packit 6bd9ab
      if (parse_dollar_substring(str, ptr, buffer, buflen, varvalue) == NULL)
Packit 6bd9ab
        return NULL;
Packit 6bd9ab
    }
Packit 6bd9ab
    else if (str[*ptr] == '#')
Packit 6bd9ab
    {
Packit 6bd9ab
      /* try to strip the remainder value from variable beginning */
Packit 6bd9ab
      (*ptr) += 1;
Packit 6bd9ab
      if (parse_dollar_match(str, ptr, buffer, buflen, varvalue) == NULL)
Packit 6bd9ab
        return NULL;
Packit 6bd9ab
    }
Packit 6bd9ab
    else
Packit 6bd9ab
      return NULL;
Packit 6bd9ab
    (*ptr)++; /* skip closing } */
Packit 6bd9ab
  }
Packit 6bd9ab
  else
Packit 6bd9ab
  {
Packit 6bd9ab
    /* it is a simple reference to a variable, like $uidNumber */
Packit 6bd9ab
    if (parse_name(str, ptr, varname, sizeof(varname)) == NULL)
Packit 6bd9ab
      return NULL;
Packit 6bd9ab
    varvalue = expander(varname, expander_arg);
Packit 6bd9ab
    if (varvalue == NULL)
Packit 6bd9ab
      varvalue = "";
Packit 6bd9ab
    if (strlen(varvalue) >= buflen)
Packit 6bd9ab
      return NULL;
Packit 6bd9ab
    strcpy(buffer, varvalue);
Packit 6bd9ab
  }
Packit 6bd9ab
  return buffer;
Packit 6bd9ab
}
Packit 6bd9ab
Packit 6bd9ab
MUST_USE static const char *parse_expression(
Packit 6bd9ab
              const char *str, int *ptr, int endat, char *buffer, size_t buflen,
Packit 6bd9ab
              expr_expander_func expander, void *expander_arg)
Packit 6bd9ab
{
Packit 6bd9ab
  int j = 0;
Packit 6bd9ab
  /* go over string */
Packit 6bd9ab
  while ((str[*ptr] != endat) && (str[*ptr] != '\0'))
Packit 6bd9ab
  {
Packit 6bd9ab
    switch (str[*ptr])
Packit 6bd9ab
    {
Packit 6bd9ab
      case '$': /* beginning of an expression */
Packit 6bd9ab
        (*ptr)++;
Packit 6bd9ab
        if ((size_t)j >= buflen)
Packit 6bd9ab
          return NULL;
Packit 6bd9ab
        if (parse_dollar_expression(str, ptr, buffer + j, buflen - j,
Packit 6bd9ab
                                    expander, expander_arg) == NULL)
Packit 6bd9ab
          return NULL;
Packit 6bd9ab
        j = strlen(buffer);
Packit 6bd9ab
        break;
Packit 6bd9ab
      case '\\': /* escaped character, unescape */
Packit 6bd9ab
        (*ptr)++;
Packit 6bd9ab
        FALLTHROUGH; /* no break needed here */
Packit 6bd9ab
      default: /* just copy the text */
Packit 6bd9ab
        if ((size_t)j >= buflen)
Packit 6bd9ab
          return NULL;
Packit 6bd9ab
        buffer[j++] = str[*ptr];
Packit 6bd9ab
        (*ptr)++;
Packit 6bd9ab
    }
Packit 6bd9ab
  }
Packit 6bd9ab
  /* NULL-terminate buffer */
Packit 6bd9ab
  if ((size_t)j >= buflen)
Packit 6bd9ab
    return NULL;
Packit 6bd9ab
  buffer[j++] = '\0';
Packit 6bd9ab
  return buffer;
Packit 6bd9ab
}
Packit 6bd9ab
Packit 6bd9ab
MUST_USE const char *expr_parse(const char *str, char *buffer, size_t buflen,
Packit 6bd9ab
                                expr_expander_func expander, void *expander_arg)
Packit 6bd9ab
{
Packit 6bd9ab
  int i = 0;
Packit 6bd9ab
  return parse_expression(str, &i, '\0', buffer, buflen,
Packit 6bd9ab
                          expander, expander_arg);
Packit 6bd9ab
}
Packit 6bd9ab
Packit 6bd9ab
SET *expr_vars(const char *str, SET *set)
Packit 6bd9ab
{
Packit 6bd9ab
  char varname[MAXVARLENGTH];
Packit 6bd9ab
  int i = 0;
Packit 6bd9ab
  /* allocate set if needed */
Packit 6bd9ab
  if (set == NULL)
Packit 6bd9ab
    set = set_new();
Packit 6bd9ab
  if (set == NULL)
Packit 6bd9ab
    return NULL;
Packit 6bd9ab
  /* go over string */
Packit 6bd9ab
  while (str[i] != '\0')
Packit 6bd9ab
  {
Packit 6bd9ab
    switch (str[i])
Packit 6bd9ab
    {
Packit 6bd9ab
      case '$': /* beginning of a $-expression */
Packit 6bd9ab
        i++;
Packit 6bd9ab
        if (str[i] == '{')
Packit 6bd9ab
          i++;
Packit 6bd9ab
        /* the rest should start with a variable name */
Packit 6bd9ab
        if (parse_name(str, &i, varname, sizeof(varname)) != NULL)
Packit 6bd9ab
          set_add(set, varname);
Packit 6bd9ab
        break;
Packit 6bd9ab
      case '\\': /* escaped character, unescape */
Packit 6bd9ab
        i++;
Packit 6bd9ab
        FALLTHROUGH; /* no break needed here */
Packit 6bd9ab
      default: /* just skip */
Packit 6bd9ab
        i++;
Packit 6bd9ab
    }
Packit 6bd9ab
  }
Packit 6bd9ab
  return set;
Packit 6bd9ab
}