Imported Upstream version 1.8.7
[debian/sudo] / plugins / sudoers / iolog_path.c
1 /*
2  * Copyright (c) 2011-2013 Todd C. Miller <Todd.Miller@courtesan.com>
3  *
4  * Permission to use, copy, modify, and distribute this software for any
5  * purpose with or without fee is hereby granted, provided that the above
6  * copyright notice and this permission notice appear in all copies.
7  *
8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15  */
16
17 #include <config.h>
18
19 #include <sys/types.h>
20 #include <stdio.h>
21 #ifdef STDC_HEADERS
22 # include <stdlib.h>
23 # include <stddef.h>
24 #else
25 # ifdef HAVE_STDLIB_H
26 #  include <stdlib.h>
27 # endif
28 #endif /* STDC_HEADERS */
29 #ifdef HAVE_STRING_H
30 # if defined(HAVE_MEMORY_H) && !defined(STDC_HEADERS)
31 #  include <memory.h>
32 # endif
33 # include <string.h>
34 #endif /* HAVE_STRING_H */
35 #ifdef HAVE_STRINGS_H
36 # include <strings.h>
37 #endif /* HAVE_STRINGS_H */
38 #include <pwd.h>
39 #include <grp.h>
40 #include <time.h>
41
42 #include "sudoers.h"
43
44 struct path_escape {
45     const char *name;
46     size_t (*copy_fn)(char *, size_t, char *);
47 };
48
49 static size_t
50 fill_seq(char *str, size_t strsize, char *logdir)
51 {
52 #ifdef SUDOERS_NO_SEQ
53     debug_decl(fill_seq, SUDO_DEBUG_UTIL)
54     debug_return_size_t(strlcpy(str, "%{seq}", strsize));
55 #else
56     static char sessid[7];
57     int len;
58     debug_decl(fill_seq, SUDO_DEBUG_UTIL)
59
60     if (sessid[0] == '\0')
61         io_nextid(logdir, def_iolog_dir, sessid);
62
63     /* Path is of the form /var/log/sudo-io/00/00/01. */
64     len = snprintf(str, strsize, "%c%c/%c%c/%c%c", sessid[0],
65         sessid[1], sessid[2], sessid[3], sessid[4], sessid[5]);
66     if (len < 0)
67         debug_return_size_t(strsize); /* handle non-standard snprintf() */
68     debug_return_size_t(len);
69 #endif /* SUDOERS_NO_SEQ */
70 }
71
72 static size_t
73 fill_user(char *str, size_t strsize, char *unused)
74 {
75     debug_decl(fill_user, SUDO_DEBUG_UTIL)
76     debug_return_size_t(strlcpy(str, user_name, strsize));
77 }
78
79 static size_t
80 fill_group(char *str, size_t strsize, char *unused)
81 {
82     struct group *grp;
83     size_t len;
84     debug_decl(fill_group, SUDO_DEBUG_UTIL)
85
86     if ((grp = sudo_getgrgid(user_gid)) != NULL) {
87         len = strlcpy(str, grp->gr_name, strsize);
88         sudo_gr_delref(grp);
89     } else {
90         len = strlen(str);
91         len = snprintf(str + len, strsize - len, "#%u",
92             (unsigned int) user_gid);
93     }
94     debug_return_size_t(len);
95 }
96
97 static size_t
98 fill_runas_user(char *str, size_t strsize, char *unused)
99 {
100     debug_decl(fill_runas_user, SUDO_DEBUG_UTIL)
101     debug_return_size_t(strlcpy(str, runas_pw->pw_name, strsize));
102 }
103
104 static size_t
105 fill_runas_group(char *str, size_t strsize, char *unused)
106 {
107     struct group *grp;
108     size_t len;
109     debug_decl(fill_runas_group, SUDO_DEBUG_UTIL)
110
111     if (runas_gr != NULL) {
112         len = strlcpy(str, runas_gr->gr_name, strsize);
113     } else {
114         if ((grp = sudo_getgrgid(runas_pw->pw_gid)) != NULL) {
115             len = strlcpy(str, grp->gr_name, strsize);
116             sudo_gr_delref(grp);
117         } else {
118             len = strlen(str);
119             len = snprintf(str + len, strsize - len, "#%u",
120                 (unsigned int) runas_pw->pw_gid);
121         }
122     }
123     debug_return_size_t(len);
124 }
125
126 static size_t
127 fill_hostname(char *str, size_t strsize, char *unused)
128 {
129     debug_decl(fill_hostname, SUDO_DEBUG_UTIL)
130     debug_return_size_t(strlcpy(str, user_shost, strsize));
131 }
132
133 static size_t
134 fill_command(char *str, size_t strsize, char *unused)
135 {
136     debug_decl(fill_command, SUDO_DEBUG_UTIL)
137     debug_return_size_t(strlcpy(str, user_base, strsize));
138 }
139
140 /* Note: "seq" must be first in the list. */
141 static struct path_escape io_path_escapes[] = {
142     { "seq", fill_seq },
143     { "user", fill_user },
144     { "group", fill_group },
145     { "runas_user", fill_runas_user },
146     { "runas_group", fill_runas_group },
147     { "hostname", fill_hostname },
148     { "command", fill_command },
149     { NULL, NULL }
150 };
151
152 /*
153  * Concatenate dir + file, expanding any escape sequences.
154  * Returns the concatenated path and sets slashp point to
155  * the path separator between the expanded dir and file.
156  */
157 char *
158 expand_iolog_path(const char *prefix, const char *dir, const char *file,
159     char **slashp)
160 {
161     size_t len, prelen = 0;
162     char *dst, *dst0, *path, *pathend, tmpbuf[PATH_MAX];
163     char *slash = NULL;
164     const char *endbrace, *src = dir;
165     struct path_escape *escapes = NULL;
166     int pass, oldlocale;
167     bool strfit;
168     debug_decl(expand_iolog_path, SUDO_DEBUG_UTIL)
169
170     /* Expanded path must be <= PATH_MAX */
171     if (prefix != NULL)
172         prelen = strlen(prefix);
173     dst = path = emalloc(prelen + PATH_MAX);
174     *path = '\0';
175     pathend = path + prelen + PATH_MAX;
176
177     /* Copy prefix, if present. */
178     if (prefix != NULL) {
179         memcpy(path, prefix, prelen);
180         dst += prelen;
181         *dst = '\0';
182     }
183
184     /* Trim leading slashes from file component. */
185     while (*file == '/')
186         file++;
187
188     for (pass = 0; pass < 3; pass++) {
189         strfit = false;
190         switch (pass) {
191         case 0:
192             src = dir;
193             escapes = io_path_escapes + 1; /* skip "%{seq}" */
194             break;
195         case 1:
196             /* Trim trailing slashes from dir component. */
197             while (dst - path - 1 > prelen && dst[-1] == '/')
198                 dst--;
199             /* The NUL will be replaced with a '/' at the end. */
200             if (dst + 1 >= pathend)
201                 goto bad;
202             slash = dst++;
203             continue;
204         case 2:
205             src = file;
206             escapes = io_path_escapes;
207             break;
208         }
209         dst0 = dst;
210         for (; *src != '\0'; src++) {
211             if (src[0] == '%') {
212                 if (src[1] == '{') {
213                     endbrace = strchr(src + 2, '}');
214                     if (endbrace != NULL) {
215                         struct path_escape *esc;
216                         len = (size_t)(endbrace - src - 2);
217                         for (esc = escapes; esc->name != NULL; esc++) {
218                             if (strncmp(src + 2, esc->name, len) == 0 &&
219                                 esc->name[len] == '\0')
220                                 break;
221                         }
222                         if (esc->name != NULL) {
223                             len = esc->copy_fn(dst, (size_t)(pathend - dst),
224                                 path + prelen);
225                             if (len >= (size_t)(pathend - dst))
226                                 goto bad;
227                             dst += len;
228                             src = endbrace;
229                             continue;
230                         }
231                     }
232                 } else if (src[1] == '%') {
233                     /* Collapse %% -> % */
234                     src++;
235                 } else {
236                     /* May need strftime() */
237                     strfit = 1;
238                 }
239             }
240             /* Need at least 2 chars, including the NUL terminator. */
241             if (dst + 1 >= pathend)
242                 goto bad;
243             *dst++ = *src;
244         }
245         *dst = '\0';
246
247         /* Expand strftime escapes as needed. */
248         if (strfit) {
249             time_t now;
250             struct tm *timeptr;
251
252             time(&now);
253             timeptr = localtime(&now);
254
255             /* Use sudoers locale for strftime() */
256             sudoers_setlocale(SUDOERS_LOCALE_SUDOERS, &oldlocale);
257
258             /* We only calls strftime() on the current part of the buffer. */
259             tmpbuf[sizeof(tmpbuf) - 1] = '\0';
260             len = strftime(tmpbuf, sizeof(tmpbuf), dst0, timeptr);
261
262             /* Restore old locale. */
263             sudoers_setlocale(oldlocale, NULL);
264
265             if (len == 0 || tmpbuf[sizeof(tmpbuf) - 1] != '\0')
266                 goto bad;               /* strftime() failed, buf too small? */
267
268             if (len >= (size_t)(pathend - dst0))
269                 goto bad;               /* expanded buffer too big to fit. */
270             memcpy(dst0, tmpbuf, len);
271             dst = dst0 + len;
272             *dst = '\0';
273         }
274     }
275     if (slashp)
276         *slashp = slash;
277     *slash = '/';
278
279     debug_return_str(path);
280 bad:
281     efree(path);
282     debug_return_str(NULL);
283 }