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