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