1/* 2 File: parse.c 3 (Linux Access Control List Management) 4 5 Copyright (C) 1999, 2000 6 Andreas Gruenbacher, <a.gruenbacher@computer.org> 7 8 This program is free software; you can redistribute it and/or 9 modify it under the terms of the GNU Library General Public 10 License as published by the Free Software Foundation; either 11 version 2 of the License, or (at your option) any later version. 12 13 This program is distributed in the hope that it will be useful, 14 but WITHOUT ANY WARRANTY; without even the implied warranty of 15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 Library General Public License for more details. 17 18 You should have received a copy of the GNU Library General Public 19 License along with this library; if not, write to the Free Software 20 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 21*/ 22 23#include <stdio.h> 24#include <stdlib.h> 25#include <string.h> 26#include <errno.h> 27 28#include <sys/types.h> 29#include <sys/stat.h> 30#include <pwd.h> 31#include <grp.h> 32#include "sys/acl.h" 33 34#include "sequence.h" 35#include "parse.h" 36#include "misc.h" 37 38#define SKIP_WS(x) ({ \ 39 while (*(x)==' ' || *(x)=='\t' || *(x)=='\n' || *(x)=='\r') \ 40 (x)++; \ 41 }) 42 43 44static int 45skip_tag_name( 46 const char **text_p, 47 const char *token) 48{ 49 size_t len = strlen(token); 50 const char *text = *text_p; 51 52 SKIP_WS(text); 53 if (strncmp(text, token, len) == 0) { 54 text += len; 55 goto delimiter; 56 } 57 if (*text == *token) { 58 text++; 59 goto delimiter; 60 } 61 return 0; 62 63delimiter: 64 SKIP_WS(text); 65 if (*text == ':') { 66 *text_p = text+1; 67 return 1; 68 } 69 if (*text == ',' || *text == '\0') { 70 *text_p = text; 71 return 1; 72 } 73 return 0; 74} 75 76 77static char * 78get_token( 79 const char **text_p) 80{ 81 char *token = NULL, *t; 82 const char *bp, *ep; 83 84 bp = *text_p; 85 SKIP_WS(bp); 86 ep = bp; 87 88 while (*ep!='\0' && *ep!='\r' && *ep!='\n' && *ep!=':' && *ep!=',') 89 ep++; 90 if (ep == bp) 91 goto after_token; 92 token = (char*)malloc(ep - bp + 1); 93 if (token == NULL) 94 goto after_token; 95 memcpy(token, bp, ep - bp); 96 97 /* Trim trailing whitespace */ 98 t = token + (ep - bp - 1); 99 while (t >= token && 100 (*t==' ' || *t=='\t' || *t=='\n' || *t=='\r')) 101 t--; 102 *(t+1) = '\0'; 103 104after_token: 105 if (*ep == ':') 106 ep++; 107 *text_p = ep; 108 return token; 109} 110 111 112static int 113get_id( 114 const char *token, 115 id_t *id_p) 116{ 117 char *ep; 118 long l; 119 l = strtol(token, &ep, 0); 120 if (*ep != '\0') 121 return -1; 122 if (l < 0) { 123 /* 124 Negative values are interpreted as 16-bit numbers, 125 so that id -2 maps to 65534 (nobody/nogroup), etc. 126 */ 127 l &= 0xFFFF; 128 } 129 *id_p = l; 130 return 0; 131} 132 133 134static int 135get_uid( 136 const char *token, 137 uid_t *uid_p) 138{ 139 struct passwd *passwd; 140 141 if (get_id(token, (id_t *)uid_p) == 0) 142 goto accept; 143 passwd = getpwnam(token); 144 if (passwd) { 145 *uid_p = passwd->pw_uid; 146 goto accept; 147 } 148 return -1; 149 150accept: 151 return 0; 152} 153 154 155static int 156get_gid( 157 const char *token, 158 gid_t *gid_p) 159{ 160 struct group *group; 161 162 if (get_id(token, (id_t *)gid_p) == 0) 163 goto accept; 164 group = getgrnam(token); 165 if (group) { 166 *gid_p = group->gr_gid; 167 goto accept; 168 } 169 return -1; 170 171accept: 172 return 0; 173} 174 175 176/* 177 Parses the next acl entry in text_p. 178 179 Returns: 180 -1 on error, 0 on success. 181*/ 182 183cmd_t 184parse_acl_cmd( 185 const char **text_p, 186 int seq_cmd, 187 int parse_mode) 188{ 189 cmd_t cmd = cmd_init(); 190 char *str; 191 const char *backup; 192 int error, perm_chars; 193 if (!cmd) 194 return NULL; 195 196 cmd->c_cmd = seq_cmd; 197 if (parse_mode & SEQ_PROMOTE_ACL) 198 cmd->c_type = ACL_TYPE_DEFAULT; 199 else 200 cmd->c_type = ACL_TYPE_ACCESS; 201 cmd->c_id = ACL_UNDEFINED_ID; 202 cmd->c_perm = 0; 203 204 if (parse_mode & SEQ_PARSE_DEFAULT) { 205 /* check for default acl entry */ 206 backup = *text_p; 207 if (skip_tag_name(text_p, "default")) { 208 if (parse_mode & SEQ_PROMOTE_ACL) { 209 /* if promoting from acl to default acl and 210 a default acl entry is found, fail. */ 211 *text_p = backup; 212 goto fail; 213 } 214 cmd->c_type = ACL_TYPE_DEFAULT; 215 } 216 } 217 218 /* parse acl entry type */ 219 switch (**text_p) { 220 case 'u': /* user */ 221 skip_tag_name(text_p, "user"); 222 223user_entry: 224 backup = *text_p; 225 str = get_token(text_p); 226 if (str) { 227 cmd->c_tag = ACL_USER; 228 error = get_uid(unquote(str), &cmd->c_id); 229 free(str); 230 if (error) { 231 *text_p = backup; 232 goto fail; 233 } 234 } else { 235 cmd->c_tag = ACL_USER_OBJ; 236 } 237 break; 238 239 case 'g': /* group */ 240 if (!skip_tag_name(text_p, "group")) 241 goto user_entry; 242 243 backup = *text_p; 244 str = get_token(text_p); 245 if (str) { 246 cmd->c_tag = ACL_GROUP; 247 error = get_gid(unquote(str), &cmd->c_id); 248 free(str); 249 if (error) { 250 *text_p = backup; 251 goto fail; 252 } 253 } else { 254 cmd->c_tag = ACL_GROUP_OBJ; 255 } 256 break; 257 258 case 'o': /* other */ 259 if (!skip_tag_name(text_p, "other")) 260 goto user_entry; 261 /* skip empty entry qualifier field (this field may 262 be missing for compatibility with Solaris.) */ 263 SKIP_WS(*text_p); 264 if (**text_p == ':') 265 (*text_p)++; 266 cmd->c_tag = ACL_OTHER; 267 break; 268 269 case 'm': /* mask */ 270 if (!skip_tag_name(text_p, "mask")) 271 goto user_entry; 272 /* skip empty entry qualifier field (this field may 273 be missing for compatibility with Solaris.) */ 274 SKIP_WS(*text_p); 275 if (**text_p == ':') 276 (*text_p)++; 277 cmd->c_tag = ACL_MASK; 278 break; 279 280 default: /* assume "user:" */ 281 goto user_entry; 282 } 283 284 SKIP_WS(*text_p); 285 if (**text_p == ',' || **text_p == '\0') { 286 if (parse_mode & SEQ_PARSE_NO_PERM) 287 return cmd; 288 else 289 goto fail; 290 } 291 if (!(parse_mode & SEQ_PARSE_WITH_PERM)) 292 return cmd; 293 294 if (parse_mode & SEQ_PARSE_WITH_RELATIVE) { 295 if (**text_p == '+') { 296 (*text_p)++; 297 cmd->c_cmd = CMD_ENTRY_ADD; 298 } else if (**text_p == '^') { 299 (*text_p)++; 300 cmd->c_cmd = CMD_ENTRY_SUBTRACT; 301 } else { 302 if (!(parse_mode & SEQ_PARSE_NO_RELATIVE)) 303 goto fail; 304 } 305 } 306 307 /* parse permissions */ 308 SKIP_WS(*text_p); 309 if (**text_p >= '0' && **text_p <= '7') { 310 cmd->c_perm = 0; 311 while (**text_p == '0') 312 (*text_p)++; 313 if (**text_p >= '1' && **text_p <= '7') { 314 cmd->c_perm = (*(*text_p)++ - '0'); 315 } 316 317 return cmd; 318 } 319 320 for (perm_chars=0; perm_chars<3; perm_chars++, (*text_p)++) { 321 switch(**text_p) { 322 case 'r': /* read */ 323 if (cmd->c_perm & CMD_PERM_READ) 324 goto fail; 325 cmd->c_perm |= CMD_PERM_READ; 326 break; 327 328 case 'w': /* write */ 329 if (cmd->c_perm & CMD_PERM_WRITE) 330 goto fail; 331 cmd->c_perm |= CMD_PERM_WRITE; 332 break; 333 334 case 'x': /* execute */ 335 if (cmd->c_perm & CMD_PERM_EXECUTE) 336 goto fail; 337 cmd->c_perm |= CMD_PERM_EXECUTE; 338 break; 339 340 case 'X': /* execute only if directory or some 341 entries already have execute permissions 342 set */ 343 if (cmd->c_perm & CMD_PERM_COND_EXECUTE) 344 goto fail; 345 cmd->c_perm |= CMD_PERM_COND_EXECUTE; 346 break; 347 348 case '-': 349 /* ignore */ 350 break; 351 352 default: 353 if (perm_chars == 0) 354 goto fail; 355 return cmd; 356 } 357 } 358 if (perm_chars != 3) 359 goto fail; 360 return cmd; 361 362fail: 363 cmd_free(cmd); 364 return NULL; 365} 366 367 368/* 369 Parse a comma-separated list of acl entries. 370 371 which is set to the index of the first character that was not parsed, 372 or -1 in case of success. 373*/ 374int 375parse_acl_seq( 376 seq_t seq, 377 const char *text_p, 378 int *which, 379 int seq_cmd, 380 int parse_mode) 381{ 382 const char *initial_text_p = text_p; 383 cmd_t cmd; 384 385 if (which) 386 *which = -1; 387 388 while (*text_p != '\0') { 389 cmd = parse_acl_cmd(&text_p, seq_cmd, parse_mode); 390 if (cmd == NULL) { 391 errno = EINVAL; 392 goto fail; 393 } 394 if (seq_append(seq, cmd) != 0) { 395 cmd_free(cmd); 396 goto fail; 397 } 398 SKIP_WS(text_p); 399 if (*text_p != ',') 400 break; 401 text_p++; 402 } 403 404 if (*text_p != '\0') { 405 errno = EINVAL; 406 goto fail; 407 } 408 409 return 0; 410 411fail: 412 if (which) 413 *which = (text_p - initial_text_p); 414 return -1; 415} 416 417 418 419int 420read_acl_comments( 421 FILE *file, 422 int *line, 423 char **path_p, 424 uid_t *uid_p, 425 gid_t *gid_p) 426{ 427 int c; 428 char linebuf[1024]; 429 char *cp; 430 char *p; 431 int comments_read = 0; 432 433 if (path_p) 434 *path_p = NULL; 435 if (uid_p) 436 *uid_p = ACL_UNDEFINED_ID; 437 if (gid_p) 438 *gid_p = ACL_UNDEFINED_ID; 439 440 for(;;) { 441 c = fgetc(file); 442 if (c == EOF) 443 break; 444 if (c==' ' || c=='\t' || c=='\r' || c=='\n') { 445 if (c=='\n') 446 (*line)++; 447 continue; 448 } 449 if (c != '#') { 450 ungetc(c, file); 451 break; 452 } 453 if (line) 454 (*line)++; 455 456 if (fgets(linebuf, sizeof(linebuf), file) == NULL) 457 break; 458 459 comments_read = 1; 460 461 p = strrchr(linebuf, '\0'); 462 while (p > linebuf && 463 (*(p-1)=='\r' || *(p-1)=='\n')) { 464 p--; 465 *p = '\0'; 466 } 467 468 cp = linebuf; 469 SKIP_WS(cp); 470 if (strncmp(cp, "file:", 5) == 0) { 471 cp += 5; 472 SKIP_WS(cp); 473 cp = unquote(cp); 474 475 if (path_p) { 476 if (*path_p) 477 goto fail; 478 *path_p = (char*)malloc(strlen(cp)+1); 479 if (!*path_p) 480 return -1; 481 strcpy(*path_p, cp); 482 } 483 } else if (strncmp(cp, "owner:", 6) == 0) { 484 cp += 6; 485 SKIP_WS(cp); 486 487 if (uid_p) { 488 if (*uid_p != ACL_UNDEFINED_ID) 489 goto fail; 490 if (get_uid(unquote(cp), uid_p) != 0) 491 continue; 492 } 493 } else if (strncmp(cp, "group:", 6) == 0) { 494 cp += 6; 495 SKIP_WS(cp); 496 497 if (gid_p) { 498 if (*gid_p != ACL_UNDEFINED_ID) 499 goto fail; 500 if (get_gid(unquote(cp), gid_p) != 0) 501 continue; 502 } 503 } 504 } 505 if (ferror(file)) 506 return -1; 507 return comments_read; 508fail: 509 if (path_p && *path_p) 510 free(*path_p); 511 return -1; 512} 513 514 515int 516read_acl_seq( 517 FILE *file, 518 seq_t seq, 519 int seq_cmd, 520 int parse_mode, 521 int *line, 522 int *which) 523{ 524 char linebuf[1024]; 525 const char *cp; 526 cmd_t cmd; 527 528 if (which) 529 *which = -1; 530 531 for(;;) { 532 if (fgets(linebuf, sizeof(linebuf), file) == NULL) 533 break; 534 if (line) 535 (*line)++; 536 537 cp = linebuf; 538 SKIP_WS(cp); 539 if (*cp == '\0') { 540 if (!(parse_mode & SEQ_PARSE_MULTI)) 541 continue; 542 break; 543 } else if (*cp == '#') { 544 continue; 545 } 546 547 cmd = parse_acl_cmd(&cp, seq_cmd, parse_mode); 548 if (cmd == NULL) { 549 errno = EINVAL; 550 goto fail; 551 } 552 if (seq_append(seq, cmd) != 0) { 553 cmd_free(cmd); 554 goto fail; 555 } 556 557 SKIP_WS(cp); 558 if (*cp != '\0' && *cp != '#') { 559 errno = EINVAL; 560 goto fail; 561 } 562 } 563 564 if (ferror(file)) 565 goto fail; 566 return 0; 567 568fail: 569 if (which) 570 *which = (cp - linebuf); 571 return -1; 572} 573 574