1 /*
2  * SPDX-License-Identifier: ISC
3  *
4  * Copyright (c) 2004-2005, 2007-2021 Todd C. Miller <Todd.Miller@sudo.ws>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 /*
20  * This is an open source non-commercial project. Dear PVS-Studio, please check it.
21  * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
22  */
23 
24 #include <config.h>
25 
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <time.h>
30 
31 #include "sudoers.h"
32 #include "sudo_lbuf.h"
33 #include <gram.h>
34 
35 /*
36  * Write the contents of a struct member to the lbuf.
37  * If alias_type is not UNSPEC, expand aliases using that type with
38  * the specified separator (which must not be NULL in the UNSPEC case).
39  */
40 static bool
sudoers_format_member_int(struct sudo_lbuf * lbuf,struct sudoers_parse_tree * parse_tree,char * name,int type,bool negated,const char * separator,int alias_type)41 sudoers_format_member_int(struct sudo_lbuf *lbuf,
42     struct sudoers_parse_tree *parse_tree, char *name, int type, bool negated,
43     const char *separator, int alias_type)
44 {
45     struct alias *a;
46     struct member *m;
47     struct sudo_command *c;
48     struct command_digest *digest;
49     debug_decl(sudoers_format_member_int, SUDOERS_DEBUG_UTIL);
50 
51     switch (type) {
52 	case MYSELF:
53 	    sudo_lbuf_append(lbuf, "%s%s", negated ? "!" : "",
54 		user_name ? user_name : "");
55 	    break;
56 	case ALL:
57 	    if (name == NULL) {
58 		sudo_lbuf_append(lbuf, "%sALL", negated ? "!" : "");
59 		break;
60 	    }
61 	    FALLTHROUGH;
62 	case COMMAND:
63 	    c = (struct sudo_command *) name;
64 	    TAILQ_FOREACH(digest, &c->digests, entries) {
65 		sudo_lbuf_append(lbuf, "%s:%s%s ",
66 		    digest_type_to_name(digest->digest_type),
67 		    digest->digest_str, TAILQ_NEXT(digest, entries) ? "," : "");
68 	    }
69 	    if (negated)
70 		sudo_lbuf_append(lbuf, "!");
71 	    sudo_lbuf_append_quoted(lbuf, SUDOERS_QUOTED" \t", "%s",
72 		c->cmnd ? c->cmnd : "ALL");
73 	    if (c->args) {
74 		sudo_lbuf_append(lbuf, " ");
75 		sudo_lbuf_append_quoted(lbuf, SUDOERS_QUOTED, "%s", c->args);
76 	    }
77 	    break;
78 	case USERGROUP:
79 	    /* Special case for %#gid, %:non-unix-group, %:#non-unix-gid */
80 	    if (strpbrk(name, " \t") == NULL) {
81 		if (*++name == ':') {
82 		    name++;
83 		    sudo_lbuf_append(lbuf, "%s", "%:");
84 		} else {
85 		    sudo_lbuf_append(lbuf, "%s", "%");
86 		}
87 	    }
88 	    goto print_word;
89 	case ALIAS:
90 	    if (alias_type != UNSPEC) {
91 		if ((a = alias_get(parse_tree, name, alias_type)) != NULL) {
92 		    TAILQ_FOREACH(m, &a->members, entries) {
93 			if (m != TAILQ_FIRST(&a->members))
94 			    sudo_lbuf_append(lbuf, "%s", separator);
95 			sudoers_format_member_int(lbuf, parse_tree, m->name,
96 			    m->type, negated ? !m->negated : m->negated,
97 			    separator, alias_type);
98 		    }
99 		    alias_put(a);
100 		    break;
101 		}
102 	    }
103 	    FALLTHROUGH;
104 	default:
105 	print_word:
106 	    /* Do not quote UID/GID, all others get quoted. */
107 	    if (name[0] == '#' &&
108 		name[strspn(name + 1, "0123456789") + 1] == '\0') {
109 		sudo_lbuf_append(lbuf, "%s%s", negated ? "!" : "", name);
110 	    } else {
111 		if (strpbrk(name, " \t") != NULL) {
112 		    sudo_lbuf_append(lbuf, "%s\"", negated ? "!" : "");
113 		    sudo_lbuf_append_quoted(lbuf, "\"", "%s", name);
114 		    sudo_lbuf_append(lbuf, "\"");
115 		} else {
116 		    sudo_lbuf_append_quoted(lbuf, SUDOERS_QUOTED, "%s%s",
117 			negated ? "!" : "", name);
118 		}
119 	    }
120 	    break;
121     }
122     debug_return_bool(!sudo_lbuf_error(lbuf));
123 }
124 
125 bool
sudoers_format_member(struct sudo_lbuf * lbuf,struct sudoers_parse_tree * parse_tree,struct member * m,const char * separator,int alias_type)126 sudoers_format_member(struct sudo_lbuf *lbuf,
127     struct sudoers_parse_tree *parse_tree, struct member *m,
128     const char *separator, int alias_type)
129 {
130     return sudoers_format_member_int(lbuf, parse_tree, m->name, m->type,
131 	m->negated, separator, alias_type);
132 }
133 
134 /*
135  * Store a defaults entry as a command tag.
136  */
137 bool
sudoers_defaults_to_tags(const char * var,const char * val,int op,struct cmndtag * tags)138 sudoers_defaults_to_tags(const char *var, const char *val, int op,
139     struct cmndtag *tags)
140 {
141     bool ret = true;
142     debug_decl(sudoers_defaults_to_tags, SUDOERS_DEBUG_UTIL);
143 
144     if (op == true || op == false) {
145 	if (strcmp(var, "authenticate") == 0) {
146 	    tags->nopasswd = op == false;
147 	} else if (strcmp(var, "sudoedit_follow") == 0) {
148 	    tags->follow = op == true;
149 	} else if (strcmp(var, "log_input") == 0) {
150 	    tags->log_input = op == true;
151 	} else if (strcmp(var, "log_output") == 0) {
152 	    tags->log_output = op == true;
153 	} else if (strcmp(var, "noexec") == 0) {
154 	    tags->noexec = op == true;
155 	} else if (strcmp(var, "intercept") == 0) {
156 	    tags->intercept = op == true;
157 	} else if (strcmp(var, "setenv") == 0) {
158 	    tags->setenv = op == true;
159 	} else if (strcmp(var, "mail_all_cmnds") == 0 ||
160 	    strcmp(var, "mail_always") == 0 ||
161 	    strcmp(var, "mail_no_perms") == 0) {
162 	    tags->send_mail = op == true;
163 	} else {
164 	    ret = false;
165 	}
166     } else {
167 	ret = false;
168     }
169     debug_return_bool(ret);
170 }
171 
172 /*
173  * Convert a defaults list to command tags.
174  */
175 bool
sudoers_defaults_list_to_tags(struct defaults_list * defs,struct cmndtag * tags)176 sudoers_defaults_list_to_tags(struct defaults_list *defs, struct cmndtag *tags)
177 {
178     bool ret = true;
179     struct defaults *d;
180     debug_decl(sudoers_defaults_list_to_tags, SUDOERS_DEBUG_UTIL);
181 
182     TAGS_INIT(tags);
183     if (defs != NULL) {
184 	TAILQ_FOREACH(d, defs, entries) {
185 	    if (!sudoers_defaults_to_tags(d->var, d->val, d->op, tags)) {
186 		if (d->val != NULL) {
187 		    sudo_debug_printf(SUDO_DEBUG_WARN,
188 			"unable to convert defaults to tag: %s%s%s", d->var,
189 			d->op == '+' ? "+=" : d->op == '-' ? "-=" : "=", d->val);
190 		} else {
191 		    sudo_debug_printf(SUDO_DEBUG_WARN,
192 			"unable to convert defaults to tag: %s%s%s",
193 			d->op == false ? "!" : "", d->var, "");
194 		}
195 		ret = false;
196 	    }
197 	}
198     }
199     debug_return_bool(ret);
200 }
201 
202 #define	FIELD_CHANGED(ocs, ncs, fld) \
203 	((ocs) == NULL || (ncs)->fld != (ocs)->fld)
204 
205 #define	TAG_CHANGED(ocs, ncs, t, tt) \
206 	(TAG_SET((t).tt) && FIELD_CHANGED(ocs, ncs, tags.tt))
207 
208 /*
209  * Write a cmndspec to lbuf in sudoers format.
210  */
211 bool
sudoers_format_cmndspec(struct sudo_lbuf * lbuf,struct sudoers_parse_tree * parse_tree,struct cmndspec * cs,struct cmndspec * prev_cs,struct cmndtag tags,bool expand_aliases)212 sudoers_format_cmndspec(struct sudo_lbuf *lbuf,
213     struct sudoers_parse_tree *parse_tree, struct cmndspec *cs,
214     struct cmndspec *prev_cs, struct cmndtag tags, bool expand_aliases)
215 {
216     debug_decl(sudoers_format_cmndspec, SUDOERS_DEBUG_UTIL);
217 
218     /* Merge privilege-level tags with cmndspec tags. */
219     TAGS_MERGE(tags, cs->tags);
220 
221 #ifdef HAVE_PRIV_SET
222     if (cs->privs != NULL && FIELD_CHANGED(prev_cs, cs, privs))
223 	sudo_lbuf_append(lbuf, "PRIVS=\"%s\" ", cs->privs);
224     if (cs->limitprivs != NULL && FIELD_CHANGED(prev_cs, cs, limitprivs))
225 	sudo_lbuf_append(lbuf, "LIMITPRIVS=\"%s\" ", cs->limitprivs);
226 #endif /* HAVE_PRIV_SET */
227 #ifdef HAVE_SELINUX
228     if (cs->role != NULL && FIELD_CHANGED(prev_cs, cs, role))
229 	sudo_lbuf_append(lbuf, "ROLE=%s ", cs->role);
230     if (cs->type != NULL && FIELD_CHANGED(prev_cs, cs, type))
231 	sudo_lbuf_append(lbuf, "TYPE=%s ", cs->type);
232 #endif /* HAVE_SELINUX */
233     if (cs->runchroot != NULL && FIELD_CHANGED(prev_cs, cs, runchroot))
234 	sudo_lbuf_append(lbuf, "CHROOT=%s ", cs->runchroot);
235     if (cs->runcwd != NULL && FIELD_CHANGED(prev_cs, cs, runcwd))
236 	sudo_lbuf_append(lbuf, "CWD=%s ", cs->runcwd);
237     if (cs->timeout > 0 && FIELD_CHANGED(prev_cs, cs, timeout)) {
238 	char numbuf[(((sizeof(int) * 8) + 2) / 3) + 2];
239 	(void)snprintf(numbuf, sizeof(numbuf), "%d", cs->timeout);
240 	sudo_lbuf_append(lbuf, "TIMEOUT=%s ", numbuf);
241     }
242     if (cs->notbefore != UNSPEC && FIELD_CHANGED(prev_cs, cs, notbefore)) {
243 	char buf[sizeof("CCYYMMDDHHMMSSZ")];
244 	struct tm *tm = gmtime(&cs->notbefore);
245 	if (strftime(buf, sizeof(buf), "%Y%m%d%H%M%SZ", tm) != 0)
246 	    sudo_lbuf_append(lbuf, "NOTBEFORE=%s ", buf);
247     }
248     if (cs->notafter != UNSPEC && FIELD_CHANGED(prev_cs, cs, notafter)) {
249 	char buf[sizeof("CCYYMMDDHHMMSSZ")];
250 	struct tm *tm = gmtime(&cs->notafter);
251 	if (strftime(buf, sizeof(buf), "%Y%m%d%H%M%SZ", tm) != 0)
252 	    sudo_lbuf_append(lbuf, "NOTAFTER=%s ", buf);
253     }
254     if (TAG_CHANGED(prev_cs, cs, tags, setenv))
255 	sudo_lbuf_append(lbuf, tags.setenv ? "SETENV: " : "NOSETENV: ");
256     if (TAG_CHANGED(prev_cs, cs, tags, intercept))
257 	sudo_lbuf_append(lbuf, tags.intercept ? "INTERCEPT: " : "NOINTERCEPT: ");
258     if (TAG_CHANGED(prev_cs, cs, tags, noexec))
259 	sudo_lbuf_append(lbuf, tags.noexec ? "NOEXEC: " : "EXEC: ");
260     if (TAG_CHANGED(prev_cs, cs, tags, nopasswd))
261 	sudo_lbuf_append(lbuf, tags.nopasswd ? "NOPASSWD: " : "PASSWD: ");
262     if (TAG_CHANGED(prev_cs, cs, tags, log_input))
263 	sudo_lbuf_append(lbuf, tags.log_input ? "LOG_INPUT: " : "NOLOG_INPUT: ");
264     if (TAG_CHANGED(prev_cs, cs, tags, log_output))
265 	sudo_lbuf_append(lbuf, tags.log_output ? "LOG_OUTPUT: " : "NOLOG_OUTPUT: ");
266     if (TAG_CHANGED(prev_cs, cs, tags, send_mail))
267 	sudo_lbuf_append(lbuf, tags.send_mail ? "MAIL: " : "NOMAIL: ");
268     if (TAG_CHANGED(prev_cs, cs, tags, follow))
269 	sudo_lbuf_append(lbuf, tags.follow ? "FOLLOW: " : "NOFOLLOW: ");
270     sudoers_format_member(lbuf, parse_tree, cs->cmnd, ", ",
271 	expand_aliases ? CMNDALIAS : UNSPEC);
272     debug_return_bool(!sudo_lbuf_error(lbuf));
273 }
274 
275 /*
276  * Format and append a defaults entry to the specified lbuf.
277  */
278 bool
sudoers_format_default(struct sudo_lbuf * lbuf,struct defaults * d)279 sudoers_format_default(struct sudo_lbuf *lbuf, struct defaults *d)
280 {
281     debug_decl(sudoers_format_default, SUDOERS_DEBUG_UTIL);
282 
283     if (d->val != NULL) {
284 	sudo_lbuf_append(lbuf, "%s%s", d->var,
285 	    d->op == '+' ? "+=" : d->op == '-' ? "-=" : "=");
286 	if (strpbrk(d->val, " \t") != NULL) {
287 	    sudo_lbuf_append(lbuf, "\"");
288 	    sudo_lbuf_append_quoted(lbuf, "\"", "%s", d->val);
289 	    sudo_lbuf_append(lbuf, "\"");
290 	} else
291 	    sudo_lbuf_append_quoted(lbuf, SUDOERS_QUOTED, "%s", d->val);
292     } else {
293 	sudo_lbuf_append(lbuf, "%s%s", d->op == false ? "!" : "", d->var);
294     }
295     debug_return_bool(!sudo_lbuf_error(lbuf));
296 }
297