Coverage Report

Created: 2025-08-09 06:12

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