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