| /* |
| File: parse.c |
| (Linux Access Control List Management) |
| |
| Copyright (C) 1999, 2000 |
| Andreas Gruenbacher, <a.gruenbacher@bestbits.at> |
| |
| This program is free software; you can redistribute it and/or |
| modify it under the terms of the GNU Lesser General Public |
| License as published by the Free Software Foundation; either |
| version 2.1 of the License, or (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| Lesser General Public License for more details. |
| |
| You should have received a copy of the GNU Lesser General Public |
| License along with this library; if not, write to the Free Software |
| Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
| */ |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <errno.h> |
| #include <limits.h> |
| |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <pwd.h> |
| #include <grp.h> |
| #include "sys/acl.h" |
| |
| #include "sequence.h" |
| #include "parse.h" |
| #include "misc.h" |
| |
| #define SKIP_WS(x) ({ \ |
| while (*(x)==' ' || *(x)=='\t' || *(x)=='\n' || *(x)=='\r') \ |
| (x)++; \ |
| }) |
| |
| |
| static int |
| skip_tag_name( |
| const char **text_p, |
| const char *token) |
| { |
| size_t len = strlen(token); |
| const char *text = *text_p; |
| |
| SKIP_WS(text); |
| if (strncmp(text, token, len) == 0) { |
| text += len; |
| goto delimiter; |
| } |
| if (*text == *token) { |
| text++; |
| goto delimiter; |
| } |
| return 0; |
| |
| delimiter: |
| SKIP_WS(text); |
| if (*text == ':') { |
| *text_p = text+1; |
| return 1; |
| } |
| if (*text == ',' || *text == '\0') { |
| *text_p = text; |
| return 1; |
| } |
| return 0; |
| } |
| |
| |
| static char * |
| get_token( |
| const char **text_p) |
| { |
| char *token = NULL, *t; |
| const char *bp, *ep; |
| |
| bp = *text_p; |
| SKIP_WS(bp); |
| ep = bp; |
| |
| while (*ep!='\0' && *ep!='\r' && *ep!='\n' && *ep!=':' && *ep!=',') |
| ep++; |
| if (ep == bp) |
| goto after_token; |
| token = (char*)malloc(ep - bp + 1); |
| if (token == NULL) |
| goto after_token; |
| memcpy(token, bp, ep - bp); |
| |
| /* Trim trailing whitespace */ |
| t = token + (ep - bp - 1); |
| while (t >= token && |
| (*t==' ' || *t=='\t' || *t=='\n' || *t=='\r')) |
| t--; |
| *(t+1) = '\0'; |
| |
| after_token: |
| if (*ep == ':') |
| ep++; |
| *text_p = ep; |
| return token; |
| } |
| |
| |
| static int |
| get_id( |
| const char *token, |
| id_t *id_p) |
| { |
| char *ep; |
| long l; |
| l = strtol(token, &ep, 0); |
| if (*ep != '\0') |
| return -1; |
| if (l < 0) { |
| /* |
| Negative values are interpreted as 16-bit numbers, |
| so that id -2 maps to 65534 (nobody/nogroup), etc. |
| */ |
| l &= 0xFFFF; |
| } |
| *id_p = l; |
| return 0; |
| } |
| |
| |
| static int |
| get_uid( |
| const char *token, |
| uid_t *uid_p) |
| { |
| struct passwd *passwd; |
| |
| if (get_id(token, (id_t *)uid_p) == 0) |
| goto accept; |
| passwd = getpwnam(token); |
| if (passwd) { |
| *uid_p = passwd->pw_uid; |
| goto accept; |
| } |
| return -1; |
| |
| accept: |
| return 0; |
| } |
| |
| |
| static int |
| get_gid( |
| const char *token, |
| gid_t *gid_p) |
| { |
| struct group *group; |
| |
| if (get_id(token, (id_t *)gid_p) == 0) |
| goto accept; |
| group = getgrnam(token); |
| if (group) { |
| *gid_p = group->gr_gid; |
| goto accept; |
| } |
| return -1; |
| |
| accept: |
| return 0; |
| } |
| |
| |
| /* |
| Parses the next acl entry in text_p. |
| |
| Returns: |
| -1 on error, 0 on success. |
| */ |
| |
| cmd_t |
| parse_acl_cmd( |
| const char **text_p, |
| int seq_cmd, |
| int parse_mode) |
| { |
| cmd_t cmd = cmd_init(); |
| char *str; |
| const char *backup; |
| int error, perm_chars; |
| if (!cmd) |
| return NULL; |
| |
| cmd->c_cmd = seq_cmd; |
| if (parse_mode & SEQ_PROMOTE_ACL) |
| cmd->c_type = ACL_TYPE_DEFAULT; |
| else |
| cmd->c_type = ACL_TYPE_ACCESS; |
| cmd->c_id = ACL_UNDEFINED_ID; |
| cmd->c_perm = 0; |
| |
| if (parse_mode & SEQ_PARSE_DEFAULT) { |
| /* check for default acl entry */ |
| backup = *text_p; |
| if (skip_tag_name(text_p, "default")) { |
| if (parse_mode & SEQ_PROMOTE_ACL) { |
| /* if promoting from acl to default acl and |
| a default acl entry is found, fail. */ |
| *text_p = backup; |
| goto fail; |
| } |
| cmd->c_type = ACL_TYPE_DEFAULT; |
| } |
| } |
| |
| /* parse acl entry type */ |
| switch (**text_p) { |
| case 'u': /* user */ |
| skip_tag_name(text_p, "user"); |
| |
| user_entry: |
| backup = *text_p; |
| str = get_token(text_p); |
| if (str) { |
| cmd->c_tag = ACL_USER; |
| error = get_uid(unquote(str), &cmd->c_id); |
| free(str); |
| if (error) { |
| *text_p = backup; |
| goto fail; |
| } |
| } else { |
| cmd->c_tag = ACL_USER_OBJ; |
| } |
| break; |
| |
| case 'g': /* group */ |
| if (!skip_tag_name(text_p, "group")) |
| goto user_entry; |
| |
| backup = *text_p; |
| str = get_token(text_p); |
| if (str) { |
| cmd->c_tag = ACL_GROUP; |
| error = get_gid(unquote(str), &cmd->c_id); |
| free(str); |
| if (error) { |
| *text_p = backup; |
| goto fail; |
| } |
| } else { |
| cmd->c_tag = ACL_GROUP_OBJ; |
| } |
| break; |
| |
| case 'o': /* other */ |
| if (!skip_tag_name(text_p, "other")) |
| goto user_entry; |
| /* skip empty entry qualifier field (this field may |
| be missing for compatibility with Solaris.) */ |
| SKIP_WS(*text_p); |
| if (**text_p == ':') |
| (*text_p)++; |
| cmd->c_tag = ACL_OTHER; |
| break; |
| |
| case 'm': /* mask */ |
| if (!skip_tag_name(text_p, "mask")) |
| goto user_entry; |
| /* skip empty entry qualifier field (this field may |
| be missing for compatibility with Solaris.) */ |
| SKIP_WS(*text_p); |
| if (**text_p == ':') |
| (*text_p)++; |
| cmd->c_tag = ACL_MASK; |
| break; |
| |
| default: /* assume "user:" */ |
| goto user_entry; |
| } |
| |
| SKIP_WS(*text_p); |
| if (**text_p == ',' || **text_p == '\0') { |
| if (parse_mode & SEQ_PARSE_NO_PERM) |
| return cmd; |
| else |
| goto fail; |
| } |
| if (!(parse_mode & SEQ_PARSE_WITH_PERM)) |
| return cmd; |
| |
| /* parse permissions */ |
| SKIP_WS(*text_p); |
| if (**text_p >= '0' && **text_p <= '7') { |
| cmd->c_perm = 0; |
| while (**text_p == '0') |
| (*text_p)++; |
| if (**text_p >= '1' && **text_p <= '7') { |
| cmd->c_perm = (*(*text_p)++ - '0'); |
| } |
| |
| return cmd; |
| } |
| |
| for (perm_chars=0; perm_chars<3; perm_chars++, (*text_p)++) { |
| switch(**text_p) { |
| case 'r': /* read */ |
| if (cmd->c_perm & CMD_PERM_READ) |
| goto fail; |
| cmd->c_perm |= CMD_PERM_READ; |
| break; |
| |
| case 'w': /* write */ |
| if (cmd->c_perm & CMD_PERM_WRITE) |
| goto fail; |
| cmd->c_perm |= CMD_PERM_WRITE; |
| break; |
| |
| case 'x': /* execute */ |
| if (cmd->c_perm & CMD_PERM_EXECUTE) |
| goto fail; |
| cmd->c_perm |= CMD_PERM_EXECUTE; |
| break; |
| |
| case 'X': /* execute only if directory or some |
| entries already have execute permissions |
| set */ |
| if (cmd->c_perm & CMD_PERM_COND_EXECUTE) |
| goto fail; |
| cmd->c_perm |= CMD_PERM_COND_EXECUTE; |
| break; |
| |
| case '-': |
| /* ignore */ |
| break; |
| |
| default: |
| if (perm_chars == 0) |
| goto fail; |
| return cmd; |
| } |
| } |
| if (perm_chars != 3) |
| goto fail; |
| return cmd; |
| |
| fail: |
| cmd_free(cmd); |
| return NULL; |
| } |
| |
| |
| /* |
| Parse a comma-separated list of acl entries. |
| |
| which is set to the index of the first character that was not parsed, |
| or -1 in case of success. |
| */ |
| int |
| parse_acl_seq( |
| seq_t seq, |
| const char *text_p, |
| int *which, |
| int seq_cmd, |
| int parse_mode) |
| { |
| const char *initial_text_p = text_p; |
| cmd_t cmd; |
| |
| if (which) |
| *which = -1; |
| |
| while (*text_p != '\0') { |
| cmd = parse_acl_cmd(&text_p, seq_cmd, parse_mode); |
| if (cmd == NULL) { |
| errno = EINVAL; |
| goto fail; |
| } |
| if (seq_append(seq, cmd) != 0) { |
| cmd_free(cmd); |
| goto fail; |
| } |
| SKIP_WS(text_p); |
| if (*text_p != ',') |
| break; |
| text_p++; |
| } |
| |
| if (*text_p != '\0') { |
| errno = EINVAL; |
| goto fail; |
| } |
| |
| return 0; |
| |
| fail: |
| if (which) |
| *which = (text_p - initial_text_p); |
| return -1; |
| } |
| |
| |
| |
| int |
| read_acl_comments( |
| FILE *file, |
| int *lineno, |
| char **path_p, |
| uid_t *uid_p, |
| gid_t *gid_p, |
| mode_t *flags) |
| { |
| int c; |
| /* |
| Max PATH_MAX bytes even for UTF-8 path names and additional 9 |
| bytes for "# file: ". Not a good solution but for now it is the |
| best I can do without too much impact on the code. [tw] |
| */ |
| char *line, *cp, *p; |
| int comments_read = 0; |
| |
| if (path_p) |
| *path_p = NULL; |
| if (uid_p) |
| *uid_p = ACL_UNDEFINED_ID; |
| if (gid_p) |
| *gid_p = ACL_UNDEFINED_ID; |
| if (flags) |
| *flags = 0; |
| |
| for(;;) { |
| c = fgetc(file); |
| if (c == EOF) |
| break; |
| if (c==' ' || c=='\t' || c=='\r' || c=='\n') { |
| if (c=='\n') |
| (*lineno)++; |
| continue; |
| } |
| if (c != '#') { |
| ungetc(c, file); |
| break; |
| } |
| if (lineno) |
| (*lineno)++; |
| |
| line = next_line(file); |
| if (line == NULL) |
| break; |
| |
| comments_read = 1; |
| |
| p = strrchr(line, '\0'); |
| while (p > line && |
| (*(p-1)=='\r' || *(p-1)=='\n')) { |
| p--; |
| *p = '\0'; |
| } |
| |
| cp = line; |
| SKIP_WS(cp); |
| if (strncmp(cp, "file:", 5) == 0) { |
| cp += 5; |
| SKIP_WS(cp); |
| cp = unquote(cp); |
| |
| if (path_p) { |
| if (*path_p) |
| goto fail; |
| *path_p = (char*)malloc(strlen(cp)+1); |
| if (!*path_p) |
| return -1; |
| strcpy(*path_p, cp); |
| } |
| } else if (strncmp(cp, "owner:", 6) == 0) { |
| cp += 6; |
| SKIP_WS(cp); |
| |
| if (uid_p) { |
| if (*uid_p != ACL_UNDEFINED_ID) |
| goto fail; |
| if (get_uid(unquote(cp), uid_p) != 0) |
| continue; |
| } |
| } else if (strncmp(cp, "group:", 6) == 0) { |
| cp += 6; |
| SKIP_WS(cp); |
| |
| if (gid_p) { |
| if (*gid_p != ACL_UNDEFINED_ID) |
| goto fail; |
| if (get_gid(unquote(cp), gid_p) != 0) |
| continue; |
| } |
| } else if (strncmp(cp, "flags:", 6) == 0) { |
| mode_t f = 0; |
| |
| cp += 6; |
| SKIP_WS(cp); |
| |
| if (cp[0] == 's') |
| f |= S_ISUID; |
| else if (cp[0] != '-') |
| goto fail; |
| if (cp[1] == 's') |
| f |= S_ISGID; |
| else if (cp[1] != '-') |
| goto fail; |
| if (cp[2] == 't') |
| f |= S_ISVTX; |
| else if (cp[2] != '-') |
| goto fail; |
| if (cp[3] != '\0') |
| goto fail; |
| |
| if (flags) |
| *flags = f; |
| } |
| } |
| if (ferror(file)) |
| return -1; |
| return comments_read; |
| fail: |
| if (path_p && *path_p) { |
| free(*path_p); |
| *path_p = NULL; |
| } |
| return -EINVAL; |
| } |
| |
| |
| int |
| read_acl_seq( |
| FILE *file, |
| seq_t seq, |
| int seq_cmd, |
| int parse_mode, |
| int *lineno, |
| int *which) |
| { |
| char *line; |
| const char *cp; |
| cmd_t cmd; |
| |
| if (which) |
| *which = -1; |
| |
| while ((line = next_line(file))) { |
| if (lineno) |
| (*lineno)++; |
| |
| cp = line; |
| SKIP_WS(cp); |
| if (*cp == '\0') { |
| if (!(parse_mode & SEQ_PARSE_MULTI)) |
| continue; |
| break; |
| } else if (*cp == '#') { |
| continue; |
| } |
| |
| cmd = parse_acl_cmd(&cp, seq_cmd, parse_mode); |
| if (cmd == NULL) { |
| errno = EINVAL; |
| goto fail; |
| } |
| if (seq_append(seq, cmd) != 0) { |
| cmd_free(cmd); |
| goto fail; |
| } |
| |
| SKIP_WS(cp); |
| if (*cp != '\0' && *cp != '#') { |
| errno = EINVAL; |
| goto fail; |
| } |
| } |
| |
| if (ferror(file)) |
| goto fail; |
| return 0; |
| |
| fail: |
| if (which) |
| *which = (cp - line); |
| return -1; |
| } |
| |