Changeset 478 in openpam


Ignore:
Timestamp:
Nov 3, 2011, 3:39:18 PM (7 years ago)
Author:
Dag-Erling Smørgrav
Message:

Major overhaul of the policy parser to support quoted option values. As a
bonus, it should now be much easier to read and understand.

This also changes the way options are stored: they are now stored as a list
of { key, value } pairs rather than "key=value" strings.

Location:
trunk/lib
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/lib/openpam_configure.c

    r474 r478  
    4949
    5050#include "openpam_impl.h"
     51#include "openpam_strlcmp.h"
    5152
    5253const char *pam_facility_name[PAM_NUM_FACILITIES] = {
     
    6869
    6970/*
    70  * Matches a word against the first one in a string.
    71  * Returns non-zero if they match.
     71 * Evaluates to non-zero if the argument is a linear whitespace character.
     72 */
     73#define is_lws(ch)                              \
     74        (ch == ' ' || ch == '\t')
     75
     76/*
     77 * Evaluates to non-zero if the argument is a printable ASCII character.
     78 * Assumes that the execution character set is a superset of ASCII.
     79 */
     80#define is_p(ch) \
     81        (ch >= '!' && ch <= '~')
     82
     83/*
     84 * Returns non-zero if the argument belongs to the POSIX Portable Filename
     85 * Character Set.  Assumes that the execution character set is a superset
     86 * of ASCII.
     87 */
     88#define is_pfcs(ch)                             \
     89        ((ch >= '0' && ch <= '9') ||            \
     90         (ch >= 'A' && ch <= 'Z') ||            \
     91         (ch >= 'a' && ch <= 'z') ||            \
     92         ch == '.' || ch == '_' || ch == '-')
     93
     94/*
     95 * Parse the service name.
     96 *
     97 * Returns the length of the service name, or 0 if the end of the string
     98 * was reached or a disallowed non-whitespace character was encountered.
     99 *
     100 * If parse_service_name() is successful, it updates *service to point to
     101 * the first character of the service name and *line to point one
     102 * character past the end.  If it reaches the end of the string, it
     103 * updates *line to point to the terminating NUL character and leaves
     104 * *service unmodified.  In all other cases, it leaves both *line and
     105 * *service unmodified.
     106 *
     107 * Allowed characters are all characters in the POSIX portable filename
     108 * character set.
    72109 */
    73110static int
    74 match_word(const char *str, const char *word)
    75 {
    76 
    77         while (*str && tolower(*str) == tolower(*word))
    78                 ++str, ++word;
    79         return ((*str == ' ' || *str == '\0') && *word == '\0');
    80 }
    81 
    82 /*
    83  * Return a pointer to the next word (or the final NUL) in a string.
    84  */
    85 static const char *
    86 next_word(const char *str)
    87 {
    88 
    89         /* skip current word */
    90         while (*str && *str != ' ')
    91                 ++str;
    92         /* skip whitespace */
    93         while (*str == ' ')
    94                 ++str;
    95         return (str);
    96 }
    97 
    98 /*
    99  * Return a malloc()ed copy of the first word in a string.
    100  */
    101 static char *
    102 dup_word(const char *str)
    103 {
    104         const char *end;
    105         char *word;
    106 
    107         for (end = str; *end && *end != ' '; ++end)
    108                 /* nothing */ ;
    109         if (asprintf(&word, "%.*s", (int)(end - str), str) < 0)
     111parse_service_name(char **line, char **service)
     112{
     113        char *b, *e;
     114
     115        for (b = *line; *b && is_lws(*b); ++b)
     116                /* nothing */ ;
     117        if (!*b) {
     118                *line = b;
     119                return (0);
     120        }
     121        for (e = b; *e && !is_lws(*e); ++e)
     122                if (!is_pfcs(*e))
     123                        return (0);
     124        if (e == b)
     125                return (0);
     126        *line = e;
     127        *service = b;
     128        return (e - b);
     129}
     130
     131/*
     132 * Parse the facility name.
     133 *
     134 * Returns the corresponding pam_facility_t value, or -1 if the end of the
     135 * string was reached, a disallowed non-whitespace character was
     136 * encountered, or the first word was not a recognized facility name.
     137 *
     138 * If parse_facility_name() is successful, it updates *line to point one
     139 * character past the end of the facility name.  If it reaches the end of
     140 * the string, it updates *line to point to the terminating NUL character.
     141 * In all other cases, it leaves *line unmodified.
     142 */
     143static pam_facility_t
     144parse_facility_name(char **line)
     145{
     146        char *b, *e;
     147        int i;
     148
     149        for (b = *line; *b && is_lws(*b); ++b)
     150                /* nothing */ ;
     151        if (!*b) {
     152                *line = b;
     153                return ((pam_facility_t)-1);
     154        }
     155        for (e = b; *e && !is_lws(*e); ++e)
     156                /* nothing */ ;
     157        if (e == b)
     158                return ((pam_facility_t)-1);
     159        for (i = 0; i < PAM_NUM_FACILITIES; ++i)
     160                if (strlcmp(pam_facility_name[i], b, e - b) == 0)
     161                        break;
     162        if (i == PAM_NUM_FACILITIES)
     163                return ((pam_facility_t)-1);
     164        *line = e;
     165        return (i);
     166}
     167
     168/*
     169 * Parse the word "include".
     170 *
     171 * If the next word on the line is "include", parse_include() updates
     172 * *line to point one character past "include" and returns 1.  Otherwise,
     173 * it leaves *line unmodified and returns 0.
     174 */
     175static int
     176parse_include(char **line)
     177{
     178        char *b, *e;
     179
     180        for (b = *line; *b && is_lws(*b); ++b)
     181                /* nothing */ ;
     182        if (!*b) {
     183                *line = b;
     184                return (-1);
     185        }
     186        for (e = b; *e && !is_lws(*e); ++e)
     187                /* nothing */ ;
     188        if (e == b)
     189                return (0);
     190        if (strlcmp("include", b, e - b) != 0)
     191                return (0);
     192        *line = e;
     193        return (1);
     194}
     195
     196/*
     197 * Parse the control flag.
     198 *
     199 * Returns the corresponding pam_control_t value, or -1 if the end of the
     200 * string was reached, a disallowed non-whitespace character was
     201 * encountered, or the first word was not a recognized control flag.
     202 *
     203 * If parse_control_flag() is successful, it updates *line to point one
     204 * character past the end of the control flag.  If it reaches the end of
     205 * the string, it updates *line to point to the terminating NUL character.
     206 * In all other cases, it leaves *line unmodified.
     207 */
     208static pam_control_t
     209parse_control_flag(char **line)
     210{
     211        char *b, *e;
     212        int i;
     213
     214        for (b = *line; *b && is_lws(*b); ++b)
     215                /* nothing */ ;
     216        if (!*b) {
     217                *line = b;
     218                return ((pam_control_t)-1);
     219        }
     220        for (e = b; *e && !is_lws(*e); ++e)
     221                /* nothing */ ;
     222        if (e == b)
     223                return ((pam_control_t)-1);
     224        for (i = 0; i < PAM_NUM_CONTROL_FLAGS; ++i)
     225                if (strlcmp(pam_control_flag_name[i], b, e - b) == 0)
     226                        break;
     227        if (i == PAM_NUM_CONTROL_FLAGS)
     228                return ((pam_control_t)-1);
     229        *line = e;
     230        return (i);
     231}
     232
     233/*
     234 * Parse a file name.
     235 *
     236 * Returns the length of the file name, or 0 if the end of the string was
     237 * reached or a disallowed non-whitespace character was encountered.
     238 *
     239 * If parse_filename() is successful, it updates *filename to point to the
     240 * first character of the filename and *line to point one character past
     241 * the end.  If it reaches the end of the string, it updates *line to
     242 * point to the terminating NUL character and leaves *filename unmodified.
     243 * In all other cases, it leaves both *line and *filename unmodified.
     244 *
     245 * Allowed characters are all characters in the POSIX portable filename
     246 * character set, plus the path separator (forward slash).
     247 */
     248static int
     249parse_filename(char **line, char **filename)
     250{
     251        char *b, *e;
     252
     253        for (b = *line; *b && is_lws(*b); ++b)
     254                /* nothing */ ;
     255        if (!*b) {
     256                *line = b;
     257                return (0);
     258        }
     259        for (e = b; *e && !is_lws(*e); ++e)
     260                if (!is_pfcs(*e) && *e != '/')
     261                        return (0);
     262        if (e == b)
     263                return (0);
     264        *line = e;
     265        *filename = b;
     266        return (e - b);
     267}
     268
     269/*
     270 * Parse an option.
     271 *
     272 * Returns a pointer to a dynamically allocated pam_opt_t with the name
     273 * and value of the next module option, or NULL if the end of the string
     274 * was reached or a disallowed non-whitespace character was encountered.
     275 *
     276 * An option consists of an option name optionally followed by an equal
     277 * sign and an option value.
     278 *
     279 * The structure and strings are allocated as a single object, so a single
     280 * free(3) call is sufficient to release the allocated memory.
     281 *
     282 * If parse_option() is successful, it updates *line to point one
     283 * character past the end of the option.  If it reaches the end of the
     284 * string, it updates *line to point to the terminating NUL character.  In
     285 * all other cases, it leaves *line unmodified.
     286 *
     287 * If parse_option() fails to allocate memory for the option structure, it
     288 * will return NULL and set errno to a non-zero value.
     289 *
     290 * Allowed characters for option names are all characters in the POSIX
     291 * portable filename character set.  Allowed characters for option values
     292 * are any printable non-whitespace characters.  The option value may be
     293 * quoted in either single or double quotes, in which case space
     294 * characters and whichever quote character was not used are allowed.
     295 * Note that the entire value must be quoted, not just part of it.
     296 */
     297static pam_opt_t *
     298parse_option(char **line)
     299{
     300        char *nb, *ne, *vb, *ve;
     301        unsigned char q = 0;
     302        pam_opt_t *opt;
     303        size_t size;
     304
     305        errno = 0;
     306        for (nb = *line; *nb && is_lws(*nb); ++nb)
     307                /* nothing */ ;
     308        if (!*nb) {
     309                *line = nb;
    110310                return (NULL);
    111         return (word);
    112 }
    113 
    114 /*
    115  * Return the length of the first word in a string.
     311        }
     312        for (ne = nb; *ne && !is_lws(*ne) && *ne != '='; ++ne)
     313                if (!is_pfcs(*ne))
     314                        return (NULL);
     315        if (ne == nb)
     316                return (NULL);
     317        if (*ne == '=') {
     318                vb = ne + 1;
     319                if (*vb == '"' || *vb == '\'')
     320                        q = *vb++;
     321                for (ve = vb;
     322                     *ve && *ve != q && (is_p(*ve) || (q && is_lws(*ve)));
     323                     ++ve)
     324                        /* nothing */ ;
     325                if (q && *ve != q)
     326                        /* non-printable character or missing endquote */
     327                        return (NULL);
     328                if (q && *(ve + 1) && !is_lws(*(ve + 1)))
     329                        /* garbage after value */
     330                        return (NULL);
     331        } else {
     332                vb = ve = ne;
     333        }
     334        size = sizeof *opt;
     335        size += ne - nb + 1;
     336        size += ve - vb + 1;
     337        if ((opt = malloc(size)) == NULL)
     338                return (NULL);
     339        opt->name = (char *)opt + sizeof *opt;
     340        strlcpy(opt->name, nb, ne - nb + 1);
     341        opt->value = opt->name + (ne - nb) + 1;
     342        strlcpy(opt->value, vb, ve - vb + 1);
     343        *line = q ? ve + 1 : ve;
     344        return (opt);
     345}
     346
     347typedef enum { pam_conf_style, pam_d_style } openpam_style_t;
     348
     349/*
     350 * Extracts given chains from a policy file.
    116351 */
    117352static int
    118 wordlen(const char *str)
    119 {
    120         int i;
    121 
    122         for (i = 0; str[i] && str[i] != ' '; ++i)
    123                 /* nothing */ ;
    124         return (i);
    125 }
    126 
    127 typedef enum { pam_conf_style, pam_d_style } openpam_style_t;
    128 
    129 /*
    130  * Extracts given chains from a policy file.
    131  */
    132 static int
    133 openpam_read_chain(pam_handle_t *pamh,
     353openpam_parse_chain(pam_handle_t *pamh,
    134354        const char *service,
    135355        pam_facility_t facility,
     
    138358{
    139359        pam_chain_t *this, **next;
    140         const char *p, *q;
    141         int count, i, lineno, ret;
     360        int count, lineno;
    142361        pam_facility_t fclt;
    143362        pam_control_t ctlf;
    144         char *line, *name;
     363        pam_opt_t *opt;
     364        char *line, *str, *name;
     365        int len, ret;
    145366        FILE *f;
    146367
     
    151372        }
    152373        this = NULL;
     374        name = NULL;
    153375        count = lineno = 0;
    154376        while ((line = openpam_readline(f, &lineno, NULL)) != NULL) {
    155                 p = line;
    156 
    157                 /* match service name */
     377                /* get service name if necessary */
    158378                if (style == pam_conf_style) {
    159                         if (!match_word(p, service)) {
     379                        if ((len = parse_service_name(&line, &str)) == 0) {
     380                                openpam_log(PAM_LOG_NOTICE,
     381                                    "%s(%d): invalid service name (ignored)",
     382                                    filename, lineno);
    160383                                FREE(line);
    161384                                continue;
    162385                        }
    163                         p = next_word(p);
    164                 }
    165 
    166                 /* match facility name */
    167                 for (fclt = 0; fclt < PAM_NUM_FACILITIES; ++fclt)
    168                         if (match_word(p, pam_facility_name[fclt]))
    169                                 break;
    170                 if (fclt == PAM_NUM_FACILITIES) {
    171                         openpam_log(PAM_LOG_NOTICE,
    172                             "%s(%d): invalid facility '%.*s' (ignored)",
    173                             filename, lineno, wordlen(p), p);
     386                        if (strlcmp(service, str, len) != 0) {
     387                                FREE(line);
     388                                continue;
     389                        }
     390                }
     391
     392                /* get facility name */
     393                if ((fclt = parse_facility_name(&line)) == (pam_facility_t)-1) {
     394                        openpam_log(PAM_LOG_ERROR,
     395                            "%s(%d): missing or invalid facility",
     396                            filename, lineno);
    174397                        goto fail;
    175398                }
     
    178401                        continue;
    179402                }
    180                 p = next_word(p);
    181 
    182                 /* include other chain */
    183                 if (match_word(p, "include")) {
    184                         p = next_word(p);
    185                         if (*next_word(p) != '\0')
    186                                 openpam_log(PAM_LOG_NOTICE,
    187                                     "%s(%d): garbage at end of 'include' line",
     403
     404                /* check for "include" */
     405                if (parse_include(&line)) {
     406                        if ((len = parse_filename(&line, &str)) == 0) {
     407                                openpam_log(PAM_LOG_ERROR,
     408                                    "%s(%d): missing or invalid filename",
    188409                                    filename, lineno);
    189                         if ((name = dup_word(p)) == NULL)
     410                                goto fail;
     411                        }
     412                        if ((name = strndup(str, len)) == NULL)
    190413                                goto syserr;
    191414                        ret = openpam_load_chain(pamh, name, fclt);
     
    198421                }
    199422
     423                /* get control flag */
     424                if ((ctlf = parse_control_flag(&line)) == (pam_control_t)-1) {
     425                        openpam_log(PAM_LOG_ERROR,
     426                            "%s(%d): missing or invalid control flag",
     427                            filename, lineno);
     428                        goto fail;
     429                }
     430
     431                /* get module name */
     432                if ((len = parse_filename(&line, &str)) == 0) {
     433                        openpam_log(PAM_LOG_ERROR,
     434                            "%s(%d): missing or invalid module name",
     435                            filename, lineno);
     436                        goto fail;
     437                }
     438                if ((name = strndup(str, len)) == NULL)
     439                        goto syserr;
     440
    200441                /* allocate new entry */
    201442                if ((this = calloc(1, sizeof *this)) == NULL)
    202443                        goto syserr;
    203 
    204                 /* control flag */
    205                 for (ctlf = 0; ctlf < PAM_NUM_CONTROL_FLAGS; ++ctlf)
    206                         if (match_word(p, pam_control_flag_name[ctlf]))
    207                                 break;
    208                 if (ctlf == PAM_NUM_CONTROL_FLAGS) {
     444                this->flag = ctlf;
     445
     446                /* get module options */
     447                /* quick and dirty, wastes a few hundred bytes */
     448                if ((this->optv = malloc(sizeof *opt * strlen(line))) == NULL)
     449                        goto syserr;
     450                while ((opt = parse_option(&line)) != NULL)
     451                        this->optv[this->optc++] = opt;
     452                this->optv[this->optc] = NULL;
     453                if (*line != '\0') {
    209454                        openpam_log(PAM_LOG_ERROR,
    210                             "%s(%d): invalid control flag '%.*s'",
    211                             filename, lineno, wordlen(p), p);
    212                         goto fail;
    213                 }
    214                 this->flag = ctlf;
    215 
    216                 /* module name */
    217                 p = next_word(p);
    218                 if (*p == '\0') {
    219                         openpam_log(PAM_LOG_ERROR,
    220                             "%s(%d): missing module name",
     455                            "%s(%d): syntax error in module options",
    221456                            filename, lineno);
    222457                        goto fail;
    223458                }
    224                 if ((name = dup_word(p)) == NULL)
    225                         goto syserr;
     459
     460                /* load module */
    226461                this->module = openpam_load_module(name);
    227462                FREE(name);
    228463                if (this->module == NULL)
    229464                        goto fail;
    230 
    231                 /* module options */
    232                 p = q = next_word(p);
    233                 while (*q != '\0') {
    234                         ++this->optc;
    235                         q = next_word(q);
    236                 }
    237                 this->optv = calloc(this->optc + 1, sizeof(char *));
    238                 if (this->optv == NULL)
    239                         goto syserr;
    240                 for (i = 0; i < this->optc; ++i) {
    241                         if ((this->optv[i] = dup_word(p)) == NULL)
    242                                 goto syserr;
    243                         p = next_word(p);
    244                 }
    245465
    246466                /* hook it up */
     
    262482        openpam_log(PAM_LOG_ERROR, "%s: %m", filename);
    263483fail:
     484        if (this && this->optc) {
     485                while (this->optc--)
     486                        FREE(this->optv[this->optc]);
     487                FREE(this->optv);
     488        }
    264489        FREE(this);
    265490        FREE(line);
     491        FREE(name);
    266492        fclose(f);
    267493        return (-1);
     
    297523                                return (-PAM_BUF_ERR);
    298524                        }
    299                         r = openpam_read_chain(pamh, service, facility,
     525                        r = openpam_parse_chain(pamh, service, facility,
    300526                            filename, pam_d_style);
    301527                        FREE(filename);
    302528                } else {
    303                         r = openpam_read_chain(pamh, service, facility,
     529                        r = openpam_parse_chain(pamh, service, facility,
    304530                            *path, pam_conf_style);
    305531                }
  • trunk/lib/openpam_get_option.c

    r437 r478  
    6060{
    6161        pam_chain_t *cur;
    62         size_t len;
    6362        int i;
    6463
     
    6766                RETURNS(NULL);
    6867        cur = pamh->current;
    69         len = strlen(option);
    70         for (i = 0; i < cur->optc; ++i) {
    71                 if (strncmp(cur->optv[i], option, len) == 0) {
    72                         if (cur->optv[i][len] == '\0')
    73                                 RETURNS(&cur->optv[i][len]);
    74                         else if (cur->optv[i][len] == '=')
    75                                 RETURNS(&cur->optv[i][len + 1]);
    76                 }
    77         }
     68        for (i = 0; i < cur->optc; ++i)
     69                if (strcmp(cur->optv[i]->name, option) == 0)
     70                        RETURNS(cur->optv[i]->value);
    7871        RETURNS(NULL);
    7972}
  • trunk/lib/openpam_impl.h

    r461 r478  
    7272} pam_facility_t;
    7373
     74typedef struct pam_opt pam_opt_t;
     75struct pam_opt {
     76        char            *name;
     77        char            *value;
     78        char             str[];
     79};
     80
    7481typedef struct pam_chain pam_chain_t;
    7582struct pam_chain {
     
    7784        int              flag;
    7885        int              optc;
    79         char           **optv;
     86        pam_opt_t      **optv;
    8087        pam_chain_t     *next;
    8188};
  • trunk/lib/openpam_set_option.c

    r437 r478  
    6363{
    6464        pam_chain_t *cur;
    65         char *opt, **optv;
    66         size_t len;
    67         int i;
     65        pam_opt_t *opt, **optv;
     66        int i, ol, vl;
    6867
    6968        ENTERS(option);
     
    7170                RETURNC(PAM_SYSTEM_ERR);
    7271        cur = pamh->current;
    73         for (len = 0; option[len] != '\0'; ++len)
    74                 if (option[len] == '=')
     72        for (i = 0; i < cur->optc; ++i)
     73                if (strcmp(cur->optv[i]->name, option) == 0)
    7574                        break;
    76         for (i = 0; i < cur->optc; ++i) {
    77                 if (strncmp(cur->optv[i], option, len) == 0 &&
    78                     (cur->optv[i][len] == '\0' || cur->optv[i][len] == '='))
    79                         break;
    80         }
    8175        if (value == NULL) {
    8276                /* remove */
     
    8680                        cur->optv[i] = cur->optv[i + 1];
    8781                cur->optv[i] = NULL;
     82                cur->optc--;
    8883                RETURNC(PAM_SUCCESS);
    8984        }
    90         if (asprintf(&opt, "%.*s=%s", (int)len, option, value) < 0)
     85        ol = strlen(option) + 1;
     86        vl = strlen(value) + 1;
     87        if ((opt = malloc(sizeof *opt + ol + vl)) == NULL)
    9188                RETURNC(PAM_BUF_ERR);
     89        opt->name = (char *)opt + sizeof *opt;
     90        strlcpy(opt->name, option, ol);
     91        opt->value = opt->name + ol;
     92        strlcpy(opt->value, value, vl);
    9293        if (i == cur->optc) {
    9394                /* add */
    94                 optv = realloc(cur->optv, sizeof(char *) * (cur->optc + 2));
     95                optv = realloc(cur->optv, sizeof *optv * (cur->optc + 2));
    9596                if (optv == NULL) {
    9697                        FREE(opt);
Note: See TracChangeset for help on using the changeset viewer.