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