Coverage Report

Created: 2025-10-13 06:08

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/sudo/plugins/sudoers/fmtsudoers.c
Line
Count
Source
1
/*
2
 * SPDX-License-Identifier: ISC
3
 *
4
 * Copyright (c) 2004-2005, 2007-2024 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
#include <config.h>
20
21
#include <stdio.h>
22
#include <stdlib.h>
23
#include <string.h>
24
#include <pwd.h>
25
#include <time.h>
26
27
#include <sudoers.h>
28
#include <sudo_lbuf.h>
29
#include <gram.h>
30
31
/*
32
 * Write the contents of a struct member to the lbuf.
33
 * If alias_type is not UNSPEC, expand aliases using that type with
34
 * the specified separator (which must not be NULL in the UNSPEC case).
35
 */
36
static bool
37
sudoers_format_member_int(struct sudo_lbuf *lbuf,
38
    const struct sudoers_parse_tree *parse_tree, const char *name, int type,
39
    bool negated, const char *separator, short alias_type)
40
252
{
41
252
    const struct sudoers_context *ctx = parse_tree->ctx;
42
252
    struct alias *a;
43
252
    const struct member *m;
44
252
    const struct sudo_command *c;
45
252
    const struct command_digest *digest;
46
252
    debug_decl(sudoers_format_member_int, SUDOERS_DEBUG_UTIL);
47
48
252
    switch (type) {
49
0
  case MYSELF:
50
0
      sudo_lbuf_append(lbuf, "%s%s", negated ? "!" : "",
51
0
    ctx->runas.list_pw ? ctx->runas.list_pw->pw_name :
52
0
    (ctx->user.name ? ctx->user.name : ""));
53
0
      break;
54
82
  case ALL:
55
82
      if (name == NULL) {
56
22
    sudo_lbuf_append(lbuf, "%sALL", negated ? "!" : "");
57
22
    break;
58
22
      }
59
60
      FALLTHROUGH;
60
110
  case COMMAND:
61
110
      c = (struct sudo_command *) name;
62
110
      TAILQ_FOREACH(digest, &c->digests, entries) {
63
36
    sudo_lbuf_append(lbuf, "%s:%s%s ",
64
36
        digest_type_to_name(digest->digest_type),
65
36
        digest->digest_str, TAILQ_NEXT(digest, entries) ? "," : "");
66
36
      }
67
110
      if (negated)
68
0
    sudo_lbuf_append(lbuf, "!");
69
110
      if (c->cmnd == NULL || c->cmnd[0] == '^') {
70
    /* No additional quoting of characters inside a regex. */
71
60
    sudo_lbuf_append(lbuf, "%s", c->cmnd ? c->cmnd : "ALL");
72
60
      } else {
73
50
    sudo_lbuf_append_quoted(lbuf, SUDOERS_QUOTED_CMD, "%s",
74
50
        c->cmnd);
75
50
      }
76
110
      if (c->args != NULL) {
77
0
    sudo_lbuf_append(lbuf, " ");
78
0
    if (c->args[0] == '^') {
79
        /* No additional quoting of characters inside a regex. */
80
0
        sudo_lbuf_append(lbuf, "%s", c->args);
81
0
    } else {
82
0
        sudo_lbuf_append_quoted(lbuf, SUDOERS_QUOTED_ARG, "%s",
83
0
      c->args);
84
0
    }
85
0
      }
86
110
      break;
87
0
  case USERGROUP:
88
      /* Special case for %#gid, %:non-unix-group, %:#non-unix-gid */
89
0
      if (strpbrk(name, " \t") == NULL) {
90
0
    if (*++name == ':') {
91
0
        name++;
92
0
        sudo_lbuf_append(lbuf, "%s", "%:");
93
0
    } else {
94
0
        sudo_lbuf_append(lbuf, "%s", "%");
95
0
    }
96
0
      }
97
0
      goto print_word;
98
66
  case ALIAS:
99
66
      if (alias_type != UNSPEC) {
100
66
    if ((a = alias_get(parse_tree, name, alias_type)) != NULL) {
101
0
        TAILQ_FOREACH(m, &a->members, entries) {
102
0
      if (m != TAILQ_FIRST(&a->members))
103
0
          sudo_lbuf_append(lbuf, "%s", separator);
104
0
      sudoers_format_member_int(lbuf, parse_tree,
105
0
          m->name, m->type,
106
0
          negated ? !m->negated : m->negated,
107
0
          separator, alias_type);
108
0
        }
109
0
        alias_put(a);
110
0
        break;
111
0
    }
112
66
      }
113
66
      FALLTHROUGH;
114
120
  default:
115
120
  print_word:
116
      /* Do not quote UID/GID, all others get quoted. */
117
120
      if (name[0] == '#' &&
118
0
    name[strspn(name + 1, "0123456789") + 1] == '\0') {
119
0
    sudo_lbuf_append(lbuf, "%s%s", negated ? "!" : "", name);
120
120
      } else {
121
120
    if (strpbrk(name, " \t") != NULL) {
122
0
        sudo_lbuf_append(lbuf, "%s\"", negated ? "!" : "");
123
0
        sudo_lbuf_append_quoted(lbuf, "\"", "%s", name);
124
0
        sudo_lbuf_append(lbuf, "\"");
125
120
    } else {
126
120
        sudo_lbuf_append_quoted(lbuf, SUDOERS_QUOTED, "%s%s",
127
120
      negated ? "!" : "", name);
128
120
    }
129
120
      }
130
120
      break;
131
252
    }
132
252
    debug_return_bool(!sudo_lbuf_error(lbuf));
133
252
}
134
135
bool
136
sudoers_format_member(struct sudo_lbuf *lbuf,
137
    const struct sudoers_parse_tree *parse_tree, const struct member *m,
138
    const char *separator, short alias_type)
139
252
{
140
252
    return sudoers_format_member_int(lbuf, parse_tree, m->name, m->type,
141
252
  m->negated, separator, alias_type);
142
252
}
143
144
/*
145
 * Store a defaults entry as a command tag.
146
 */
147
bool
148
sudoers_defaults_to_tags(const char *var, const char *val, int op,
149
    struct cmndtag *tags)
150
0
{
151
0
    bool ret = true;
152
0
    debug_decl(sudoers_defaults_to_tags, SUDOERS_DEBUG_UTIL);
153
154
0
    if (op == true || op == false) {
155
0
  if (strcmp(var, "authenticate") == 0) {
156
0
      tags->nopasswd = op == false;
157
0
  } else if (strcmp(var, "sudoedit_follow") == 0) {
158
0
      tags->follow = op == true;
159
0
  } else if (strcmp(var, "log_input") == 0) {
160
0
      tags->log_input = op == true;
161
0
  } else if (strcmp(var, "log_output") == 0) {
162
0
      tags->log_output = op == true;
163
0
  } else if (strcmp(var, "noexec") == 0) {
164
0
      tags->noexec = op == true;
165
0
  } else if (strcmp(var, "intercept") == 0) {
166
0
      tags->intercept = op == true;
167
0
  } else if (strcmp(var, "setenv") == 0) {
168
0
      tags->setenv = op == true;
169
0
  } else if (strcmp(var, "mail_all_cmnds") == 0 ||
170
0
      strcmp(var, "mail_always") == 0 ||
171
0
      strcmp(var, "mail_no_perms") == 0) {
172
0
      tags->send_mail = op == true;
173
0
  } else {
174
0
      ret = false;
175
0
  }
176
0
    } else {
177
0
  ret = false;
178
0
    }
179
0
    debug_return_bool(ret);
180
0
}
181
182
/*
183
 * Convert a defaults list to command tags.
184
 */
185
bool
186
sudoers_defaults_list_to_tags(const struct defaults_list *defs,
187
    struct cmndtag *tags)
188
33
{
189
33
    const struct defaults *d;
190
33
    bool ret = true;
191
33
    debug_decl(sudoers_defaults_list_to_tags, SUDOERS_DEBUG_UTIL);
192
193
33
    TAGS_INIT(tags);
194
33
    if (defs != NULL) {
195
33
  TAILQ_FOREACH(d, defs, entries) {
196
0
      if (!sudoers_defaults_to_tags(d->var, d->val, d->op, tags)) {
197
0
    if (d->val != NULL) {
198
0
        sudo_debug_printf(SUDO_DEBUG_WARN,
199
0
      "unable to convert defaults to tag: %s%s%s", d->var,
200
0
      d->op == '+' ? "+=" : d->op == '-' ? "-=" : "=", d->val);
201
0
    } else {
202
0
        sudo_debug_printf(SUDO_DEBUG_WARN,
203
0
      "unable to convert defaults to tag: %s%s%s",
204
0
      d->op == false ? "!" : "", d->var, "");
205
0
    }
206
0
    ret = false;
207
0
      }
208
0
  }
209
33
    }
210
33
    debug_return_bool(ret);
211
33
}
212
213
#define FIELD_CHANGED(ocs, ncs, fld) \
214
10
  ((ocs) == NULL || (ncs)->fld != (ocs)->fld)
215
216
#define TAG_CHANGED(ocs, ncs, t, tt) \
217
328
  (TAG_SET((t).tt) && FIELD_CHANGED(ocs, ncs, tags.tt))
218
219
/*
220
 * Write a cmndspec to lbuf in sudoers format.
221
 */
222
bool
223
sudoers_format_cmndspec(struct sudo_lbuf *lbuf,
224
    const struct sudoers_parse_tree *parse_tree, const struct cmndspec *cs,
225
    const struct cmndspec *prev_cs, struct cmndtag tags, bool expand_aliases)
226
41
{
227
41
    debug_decl(sudoers_format_cmndspec, SUDOERS_DEBUG_UTIL);
228
229
    /* Merge privilege-level tags with cmndspec tags. */
230
41
    TAGS_MERGE(tags, cs->tags);
231
232
41
    if (cs->privs != NULL && FIELD_CHANGED(prev_cs, cs, privs))
233
0
  sudo_lbuf_append(lbuf, "PRIVS=\"%s\" ", cs->privs);
234
41
    if (cs->limitprivs != NULL && FIELD_CHANGED(prev_cs, cs, limitprivs))
235
0
  sudo_lbuf_append(lbuf, "LIMITPRIVS=\"%s\" ", cs->limitprivs);
236
41
    if (cs->role != NULL && FIELD_CHANGED(prev_cs, cs, role))
237
0
  sudo_lbuf_append(lbuf, "ROLE=%s ", cs->role);
238
41
    if (cs->type != NULL && FIELD_CHANGED(prev_cs, cs, type))
239
0
  sudo_lbuf_append(lbuf, "TYPE=%s ", cs->type);
240
41
    if (cs->apparmor_profile != NULL && FIELD_CHANGED(prev_cs, cs, apparmor_profile))
241
0
  sudo_lbuf_append(lbuf, "APPARMOR_PROFILE=%s ", cs->apparmor_profile);
242
41
    if (cs->runchroot != NULL && FIELD_CHANGED(prev_cs, cs, runchroot))
243
4
  sudo_lbuf_append(lbuf, "CHROOT=%s ", cs->runchroot);
244
41
    if (cs->runcwd != NULL && FIELD_CHANGED(prev_cs, cs, runcwd))
245
2
  sudo_lbuf_append(lbuf, "CWD=%s ", cs->runcwd);
246
41
    if (cs->timeout > 0 && FIELD_CHANGED(prev_cs, cs, timeout)) {
247
0
  char numbuf[STRLEN_MAX_SIGNED(int) + 1];
248
0
  (void)snprintf(numbuf, sizeof(numbuf), "%d", cs->timeout);
249
0
  sudo_lbuf_append(lbuf, "TIMEOUT=%s ", numbuf);
250
0
    }
251
41
    if (cs->notbefore != UNSPEC && FIELD_CHANGED(prev_cs, cs, notbefore)) {
252
0
  char buf[sizeof("CCYYMMDDHHMMSSZ")] = "";
253
0
  struct tm gmt;
254
0
  if (gmtime_r(&cs->notbefore, &gmt) != NULL) {
255
0
      size_t len = strftime(buf, sizeof(buf), "%Y%m%d%H%M%SZ", &gmt);
256
0
      if (len != 0 && buf[sizeof(buf) - 1] == '\0')
257
0
    sudo_lbuf_append(lbuf, "NOTBEFORE=%s ", buf);
258
0
  }
259
0
    }
260
41
    if (cs->notafter != UNSPEC && FIELD_CHANGED(prev_cs, cs, notafter)) {
261
0
  char buf[sizeof("CCYYMMDDHHMMSSZ")] = "";
262
0
  struct tm gmt;
263
0
  if (gmtime_r(&cs->notafter, &gmt) != NULL) {
264
0
      size_t len = strftime(buf, sizeof(buf), "%Y%m%d%H%M%SZ", &gmt);
265
0
      if (len != 0 && buf[sizeof(buf) - 1] == '\0')
266
0
    sudo_lbuf_append(lbuf, "NOTAFTER=%s ", buf);
267
0
  }
268
0
    }
269
41
    if (TAG_CHANGED(prev_cs, cs, tags, setenv))
270
0
  sudo_lbuf_append(lbuf, tags.setenv ? "SETENV: " : "NOSETENV: ");
271
41
    if (TAG_CHANGED(prev_cs, cs, tags, intercept))
272
0
  sudo_lbuf_append(lbuf, tags.intercept ? "INTERCEPT: " : "NOINTERCEPT: ");
273
41
    if (TAG_CHANGED(prev_cs, cs, tags, noexec))
274
0
  sudo_lbuf_append(lbuf, tags.noexec ? "NOEXEC: " : "EXEC: ");
275
41
    if (TAG_CHANGED(prev_cs, cs, tags, nopasswd))
276
0
  sudo_lbuf_append(lbuf, tags.nopasswd ? "NOPASSWD: " : "PASSWD: ");
277
41
    if (TAG_CHANGED(prev_cs, cs, tags, log_input))
278
0
  sudo_lbuf_append(lbuf, tags.log_input ? "LOG_INPUT: " : "NOLOG_INPUT: ");
279
41
    if (TAG_CHANGED(prev_cs, cs, tags, log_output))
280
0
  sudo_lbuf_append(lbuf, tags.log_output ? "LOG_OUTPUT: " : "NOLOG_OUTPUT: ");
281
41
    if (TAG_CHANGED(prev_cs, cs, tags, send_mail))
282
0
  sudo_lbuf_append(lbuf, tags.send_mail ? "MAIL: " : "NOMAIL: ");
283
41
    if (TAG_CHANGED(prev_cs, cs, tags, follow))
284
0
  sudo_lbuf_append(lbuf, tags.follow ? "FOLLOW: " : "NOFOLLOW: ");
285
41
    sudoers_format_member(lbuf, parse_tree, cs->cmnd, ", ",
286
41
  expand_aliases ? CMNDALIAS : UNSPEC);
287
41
    debug_return_bool(!sudo_lbuf_error(lbuf));
288
41
}
289
290
/*
291
 * Format and append a defaults entry to the specified lbuf.
292
 */
293
bool
294
sudoers_format_default(struct sudo_lbuf *lbuf, const struct defaults *d)
295
5.57k
{
296
5.57k
    debug_decl(sudoers_format_default, SUDOERS_DEBUG_UTIL);
297
298
5.57k
    if (d->val != NULL) {
299
184
  sudo_lbuf_append(lbuf, "%s%s", d->var,
300
184
      d->op == '+' ? "+=" : d->op == '-' ? "-=" : "=");
301
184
  if (strpbrk(d->val, " \t") != NULL) {
302
0
      sudo_lbuf_append(lbuf, "\"");
303
0
      sudo_lbuf_append_quoted(lbuf, "\"", "%s", d->val);
304
0
      sudo_lbuf_append(lbuf, "\"");
305
0
  } else
306
184
      sudo_lbuf_append_quoted(lbuf, SUDOERS_QUOTED, "%s", d->val);
307
5.38k
    } else {
308
5.38k
  sudo_lbuf_append(lbuf, "%s%s", d->op == false ? "!" : "", d->var);
309
5.38k
    }
310
    debug_return_bool(!sudo_lbuf_error(lbuf));
311
5.57k
}