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