xref: /openbsd/usr.bin/rsync/mktemp.c (revision d89ec533)
1 /*	$OpenBSD: mktemp.c,v 1.11 2019/06/27 18:03:37 deraadt Exp $ */
2 /*
3  * Copyright (c) 1996-1998, 2008 Theo de Raadt
4  * Copyright (c) 1997, 2008-2009 Todd C. Miller
5  * Copyright (c) 2019 Florian Obser <florian@openbsd.org>
6  *
7  * Permission to use, copy, modify, and distribute this software for any
8  * purpose with or without fee is hereby granted, provided that the above
9  * copyright notice and this permission notice appear in all copies.
10  *
11  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18  */
19 
20 #include <sys/types.h>
21 #include <sys/stat.h>
22 #include <sys/socket.h>
23 #include <sys/un.h>
24 
25 #include <errno.h>
26 #include <fcntl.h>
27 #include <limits.h>
28 #include <stdio.h>
29 #include <stdint.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <ctype.h>
33 #include <unistd.h>
34 
35 #include "extern.h"
36 
37 /*
38  * The type of temporary files we can create.
39  */
40 enum	tmpmode {
41 	MKTEMP_NAME,
42 	MKTEMP_FILE,
43 	MKTEMP_DIR,
44 	MKTEMP_LINK,
45 	MKTEMP_FIFO,
46 	MKTEMP_NOD,
47 	MKTEMP_SOCK
48 };
49 
50 /*
51  * Characters we'll use for replacement in the template string.
52  */
53 #define TEMPCHARS	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
54 #define NUM_CHARS	(sizeof(TEMPCHARS) - 1)
55 
56 /*
57  * The number of template replacement values (foo.XXXXXX = 6) that we
58  * require as a minimum for the filename.
59  */
60 #define MIN_X		6
61 
62 /*
63  * The only flags we'll accept for creation of the temporary file.
64  */
65 #define MKOTEMP_FLAGS	(O_APPEND | O_CLOEXEC | O_DSYNC | O_RSYNC | O_SYNC)
66 
67 #ifndef nitems
68 #define nitems(_a)	(sizeof((_a)) / sizeof((_a)[0]))
69 #endif
70 
71 /*
72  * Adapted from libc/stdio/mktemp.c.
73  */
74 static int
75 mktemp_internalat(int pfd, char *path, int slen, enum tmpmode mode,
76 	int flags, const char *link, mode_t dev_type, dev_t dev)
77 {
78 	char		*start, *cp, *ep;
79 	const char	 tempchars[] = TEMPCHARS;
80 	unsigned int	 tries;
81 	struct stat	 sb;
82 	struct sockaddr_un sun;
83 	size_t		 len;
84 	int		 fd, saved_errno;
85 
86 	len = strlen(path);
87 	if (len < MIN_X || slen < 0 || (size_t)slen > len - MIN_X) {
88 		errno = EINVAL;
89 		return(-1);
90 	}
91 	ep = path + len - slen;
92 
93 	for (start = ep; start > path && start[-1] == 'X'; start--)
94 		/* continue */ ;
95 
96 	if (ep - start < MIN_X) {
97 		errno = EINVAL;
98 		return(-1);
99 	}
100 
101 	if (flags & ~MKOTEMP_FLAGS) {
102 		errno = EINVAL;
103 		return(-1);
104 	}
105 	flags |= O_CREAT | O_EXCL | O_RDWR;
106 
107 	tries = INT_MAX;
108 	do {
109 		cp = start;
110 		do {
111 			unsigned short rbuf[16];
112 			unsigned int i;
113 
114 			/*
115 			 * Avoid lots of arc4random() calls by using
116 			 * a buffer sized for up to 16 Xs at a time.
117 			 */
118 			arc4random_buf(rbuf, sizeof(rbuf));
119 			for (i = 0; i < nitems(rbuf) && cp != ep; i++)
120 				*cp++ = tempchars[rbuf[i] % NUM_CHARS];
121 		} while (cp != ep);
122 
123 		switch (mode) {
124 		case MKTEMP_NAME:
125 			if (fstatat(pfd, path, &sb, AT_SYMLINK_NOFOLLOW) != 0)
126 				return(errno == ENOENT ? 0 : -1);
127 			break;
128 		case MKTEMP_FILE:
129 			fd = openat(pfd, path, flags, S_IRUSR|S_IWUSR);
130 			if (fd != -1 || errno != EEXIST)
131 				return(fd);
132 			break;
133 		case MKTEMP_DIR:
134 			if (mkdirat(pfd, path, S_IRUSR|S_IWUSR|S_IXUSR) == 0)
135 				return(0);
136 			if (errno != EEXIST)
137 				return(-1);
138 			break;
139 		case MKTEMP_LINK:
140 			if (symlinkat(link, pfd, path) == 0)
141 				return(0);
142 			else if (errno != EEXIST)
143 				return(-1);
144 			break;
145 		case MKTEMP_FIFO:
146 			if (mkfifoat(pfd, path, S_IRUSR|S_IWUSR) == 0)
147 				return(0);
148 			else if (errno != EEXIST)
149 				return(-1);
150 			break;
151 		case MKTEMP_NOD:
152 			if (!(dev_type == S_IFCHR || dev_type == S_IFBLK)) {
153 				errno = EINVAL;
154 				return(-1);
155 			}
156 			if (mknodat(pfd, path, S_IRUSR|S_IWUSR|dev_type, dev)
157 			    == 0)
158 				return(0);
159 			else if (errno != EEXIST)
160 				return(-1);
161 			break;
162 		case MKTEMP_SOCK:
163 			memset(&sun, 0, sizeof(sun));
164 			sun.sun_family = AF_UNIX;
165 			if ((len = strlcpy(sun.sun_path, link,
166 			    sizeof(sun.sun_path))) >= sizeof(sun.sun_path)) {
167 				errno = EINVAL;
168 				return(-1);
169 			}
170 			if (sun.sun_path[len] != '/') {
171 				if (strlcat(sun.sun_path, "/",
172 				    sizeof(sun.sun_path)) >=
173 				    sizeof(sun.sun_path)) {
174 					errno = EINVAL;
175 					return(-1);
176 				}
177 			}
178 			if (strlcat(sun.sun_path, path, sizeof(sun.sun_path)) >=
179 			    sizeof(sun.sun_path)) {
180 				errno = EINVAL;
181 				return(-1);
182 			}
183 			if ((fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC |
184 			    SOCK_NONBLOCK, 0)) == -1)
185 				return -1;
186 			if (bind(fd, (struct sockaddr *)&sun, sizeof(sun)) ==
187 			    0) {
188 				close(fd);
189 				return(0);
190 			} else if (errno != EEXIST) {
191 					saved_errno = errno;
192 					close(fd);
193 					errno = saved_errno;
194 					return -1;
195 			}
196 			close(fd);
197 			break;
198 		}
199 	} while (--tries);
200 
201 	errno = EEXIST;
202 	return(-1);
203 }
204 
205 /*
206  * A combination of mkstemp(3) and openat(2).
207  * On success returns a file descriptor and trailing Xs are overwritten in
208  * path to create a unique file name.
209  * Returns -1 on failure and sets errno.
210  */
211 int
212 mkstempat(int fd, char *path)
213 {
214 	return mktemp_internalat(fd, path, 0, MKTEMP_FILE, 0, NULL, 0, 0);
215 }
216 
217 /*
218  * A combination of mkstemp(3) and symlinkat(2).
219  * On success returns path with trailing Xs overwritten to create a unique
220  * file name.
221  * Returns NULL on failure and sets errno.
222  */
223 char *
224 mkstemplinkat(char *link, int fd, char *path)
225 {
226 
227 	if (mktemp_internalat(fd, path, 0, MKTEMP_LINK, 0, link, 0, 0) == -1)
228 		return NULL;
229 	return path;
230 }
231 
232 /*
233  * A combination of mkstemp(3) and mkfifoat(2).
234  * On success returns path with trailing Xs overwritten to create a unique
235  * file name.
236  * Returns NULL on failure and sets errno.
237  */
238 char *
239 mkstempfifoat(int fd, char *path)
240 {
241 
242 	if (mktemp_internalat(fd, path, 0, MKTEMP_FIFO, 0, NULL, 0, 0) == -1)
243 		return NULL;
244 	return path;
245 }
246 
247 /*
248  * A combination of mkstemp(3) and mknodat(2).
249  * On success returns path with trailing Xs overwritten to create a unique
250  * file name.
251  * Returns NULL on failure and sets errno.
252  */
253 char *
254 mkstempnodat(int fd, char *path, mode_t mode, dev_t dev)
255 {
256 
257 	if (mktemp_internalat(fd, path, 0,
258 	    MKTEMP_NOD, 0, NULL, mode, dev) == -1)
259 		return NULL;
260 	return path;
261 }
262 
263 /*
264  * A combination of mkstemp(3) and bind(2) on a unix domain socket.
265  * On success returns path with trailing Xs overwritten to create a unique
266  * file name.
267  * Returns NULL on failure and sets errno.
268  */
269 char *
270 mkstempsock(const char *root, char *path)
271 {
272 
273 	if (mktemp_internalat(0, path, 0, MKTEMP_SOCK, 0, root, 0, 0) == -1)
274 		return NULL;
275 	return path;
276 }
277 
278 /*
279  * Turn path into a suitable template for mkstemp*at functions and
280  * place it into the newly allocated string returned in ret.
281  * The caller must free ret.
282  * Returns -1 on failure or number of characters output to ret
283  * (excluding the final '\0').
284  */
285 int
286 mktemplate(char **ret, const char *path, int recursive)
287 {
288 	int		 n, dirlen;
289 	const char	*cp;
290 
291 	if (recursive && (cp = strrchr(path, '/')) != NULL) {
292 		dirlen = cp - path;
293 		n = asprintf(ret, "%.*s/.%s.XXXXXXXXXX",
294 			dirlen, path, path + dirlen + 1);
295 		if (n == -1) {
296 			ERR("asprintf");
297 			*ret = NULL;
298 		}
299 	} else if ((n = asprintf(ret, ".%s.XXXXXXXXXX", path)) == -1) {
300 		ERR("asprintf");
301 		*ret = NULL;
302 	}
303 
304 	return n;
305 }
306