xref: /illumos-gate/usr/src/lib/libc/port/rt/pos4obj.c (revision 3db86aab)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 #include "synonyms.h"
30 #include "mtlib.h"
31 #include <sys/types.h>
32 #include <errno.h>
33 #include <fcntl.h>
34 #include <sys/stat.h>
35 #include <unistd.h>
36 #include <stdlib.h>
37 #include <limits.h>
38 #include <pthread.h>
39 #include <thread.h>
40 #include <string.h>
41 #include <dirent.h>
42 #include <stdio.h>
43 #include <dlfcn.h>
44 #include <md5.h>
45 #include "pos4obj.h"
46 
47 #define	HASHSTRLEN	32
48 
49 static	char	*__pos4obj_name(const char *, const char *);
50 static	void	__pos4obj_md5toa(unsigned char *, unsigned char *);
51 static	void	__pos4obj_clean(char *);
52 
53 static	char	objroot[] = "/tmp/";
54 static	long int	name_max = 0;
55 
56 int
57 __open_nc(const char *path, int oflag, mode_t mode)
58 {
59 	int		canstate, val;
60 	struct stat64	statbuf;
61 
62 	/*
63 	 * Ensure path is not a symlink to somewhere else. This provides
64 	 * a modest amount of protection against easy security attacks.
65 	 */
66 	if (lstat64(path, &statbuf) == 0) {
67 		if (S_ISLNK(statbuf.st_mode)) {
68 			errno = EINVAL;
69 			return (-1);
70 		}
71 	}
72 
73 	(void) pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &canstate);
74 	val = open64(path, oflag, mode);
75 	(void) pthread_setcancelstate(canstate, &canstate);
76 
77 	return (val);
78 }
79 
80 int
81 __close_nc(int fildes)
82 {
83 	int	canstate, val;
84 
85 	(void) pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &canstate);
86 	val = close(fildes);
87 	(void) pthread_setcancelstate(canstate, &canstate);
88 
89 	return (val);
90 }
91 
92 /*
93  * This is to avoid loading libmd.so.1 unless we absolutely have to.
94  */
95 typedef void (*md5_calc_t)(unsigned char *, unsigned char *, unsigned int);
96 static void *md5_handle = NULL;
97 static md5_calc_t real_md5_calc = NULL;
98 static mutex_t md5_lock = DEFAULTMUTEX;
99 
100 static void
101 load_md5_calc(void)
102 {
103 	lmutex_lock(&md5_lock);
104 	if (real_md5_calc == NULL) {
105 		md5_handle = dlopen("libmd.so.1", RTLD_LAZY);
106 		if (md5_handle == NULL)
107 			real_md5_calc = (md5_calc_t)(-1);
108 		else {
109 			real_md5_calc =
110 			    (md5_calc_t)dlsym(md5_handle, "md5_calc");
111 			if (real_md5_calc == NULL) {
112 				(void) dlclose(md5_handle);
113 				md5_handle = NULL;
114 				real_md5_calc = (md5_calc_t)(-1);
115 			}
116 		}
117 	}
118 	lmutex_unlock(&md5_lock);
119 }
120 
121 static char *
122 __pos4obj_name(const char *path, const char *type)
123 {
124 	int	shortpath = 1;
125 	int	olderrno;
126 	size_t	len;
127 	char	*dfile;
128 	unsigned char	hashbuf[HASHSTRLEN + 1];
129 	unsigned char	md5_digest[MD5_DIGEST_LENGTH];
130 
131 	/*
132 	 * If the path is path_max - strlen(type) characters or less,
133 	 * the name of the file to use will be the path prefixed by
134 	 * the type.
135 	 *
136 	 * In the special case where the path is longer than
137 	 * path_max - strlen(type) characters, we create a string based on the
138 	 * MD5 hash of the path. We prefix that string with a '.' to
139 	 * make it obscure, and create a directory in objroot with
140 	 * that name. In that directory, we create a directory named
141 	 * after the type of object requested.  Inside the type
142 	 * directory, the filename will be the path of the object. This
143 	 * prevents collisions in all namespaces.
144 	 *
145 	 * Example:
146 	 * Let objroot = "/tmp/", path = "/<longpath>", and type = ".MQD"
147 	 * Let the MD5 hash of "<longpath>" = "<hash>"
148 	 *
149 	 * The desired file is /tmp/.<hash>/.MQD/<longpath>
150 	 */
151 
152 	/*
153 	 * Do not include the leading '/' in the path length.
154 	 * Assumes __pos4obj_check(path) has already been called.
155 	 */
156 	if ((strlen(path) - 1) > (name_max - strlen(type)))
157 		shortpath = 0;
158 
159 	if (shortpath) {
160 		/*
161 		 * strlen(path) includes leading slash as space for NUL.
162 		 */
163 		len = strlen(objroot) + strlen(type) + strlen(path);
164 	} else {
165 		/*
166 		 * Long path name. Add 3 for extra '/', '.' and '\0'
167 		 */
168 		len = strlen(objroot) + HASHSTRLEN + strlen(type) +
169 		    strlen(path) + 3;
170 	}
171 
172 	if ((dfile = malloc(len)) == NULL)
173 		return (NULL);
174 
175 	(void) memset(dfile, 0, len);
176 	(void) strcpy(dfile, objroot);
177 
178 	if (shortpath) {
179 		(void) strcat(dfile, type);
180 		(void) strcat(dfile, path + 1);
181 		return (dfile);
182 	}
183 
184 	/*
185 	 * If we can successfully load it, call md5_calc().
186 	 * Otherwise, (this "can't happen") return NULL.
187 	 */
188 	if (real_md5_calc == NULL)
189 		load_md5_calc();
190 	if (real_md5_calc == (md5_calc_t)(-1)) {
191 		free(dfile);
192 		return (NULL);
193 	}
194 
195 	real_md5_calc(md5_digest, (unsigned char *)path + 1, strlen(path + 1));
196 	__pos4obj_md5toa(hashbuf, md5_digest);
197 	(void) strcat(dfile, ".");
198 	(void) strcat(dfile, (const char *)hashbuf);
199 
200 	/*
201 	 * Errno must be preserved across the following calls to
202 	 * mkdir.  This needs to be done to prevent incorrect error
203 	 * reporting in certain cases. When we attempt to open a
204 	 * non-existent object without the O_CREAT flag, it will
205 	 * always create a lock file first.  The lock file is created
206 	 * and then the open is attempted, but fails with ENOENT. The
207 	 * lock file is then destroyed. In the following code path, we
208 	 * are finding the absolute path to the lock file after
209 	 * already having attempted the open (which set errno to
210 	 * ENOENT). The following calls to mkdir will return -1 and
211 	 * set errno to EEXIST, since the hash and type directories
212 	 * were created when the lock file was created. The correct
213 	 * errno is the ENOENT from the attempted open of the desired
214 	 * object.
215 	 */
216 	olderrno = errno;
217 
218 	/*
219 	 * Create hash directory. Use 777 permissions so everyone can use it.
220 	 */
221 	if (mkdir(dfile, S_IRWXU|S_IRWXG|S_IRWXO) == 0) {
222 		if (chmod(dfile, S_IRWXU|S_IRWXG|S_IRWXO) == -1) {
223 			free(dfile);
224 			return (NULL);
225 		}
226 	} else {
227 		if (errno != EEXIST) {
228 			free(dfile);
229 			return (NULL);
230 		}
231 	}
232 
233 	(void) strcat(dfile, "/");
234 	(void) strcat(dfile, type);
235 
236 	/*
237 	 * Create directory for requested type. Use 777 perms so everyone
238 	 * can use it.
239 	 */
240 	if (mkdir(dfile, S_IRWXU|S_IRWXG|S_IRWXO) == 0) {
241 		if (chmod(dfile, S_IRWXU|S_IRWXG|S_IRWXO) == -1) {
242 			free(dfile);
243 			return (NULL);
244 		}
245 	} else {
246 		if (errno != EEXIST) {
247 			free(dfile);
248 			return (NULL);
249 		}
250 	}
251 
252 	errno = olderrno;
253 	(void) strcat(dfile, path);
254 	return (dfile);
255 }
256 
257 /*
258  * Takes a 128-bit MD5 digest and transforms to a sequence of 32 ASCII
259  * characters. Output is the hexadecimal representation of the digest.
260  *
261  * The output buffer must be at least HASHSTRLEN + 1 characters
262  * long.  HASHSTRLEN is the size of the MD5 digest (128 bits)
263  * divided by the number of bits used per char of output (4). The
264  * extra character at the end is for the NUL terminating character.
265  */
266 
267 static void
268 __pos4obj_md5toa(unsigned char *dest, unsigned char *src)
269 {
270 	int i;
271 	uint32_t *p;
272 
273 	/* LINTED pointer cast may result in improper alignment */
274 	p = (uint32_t *)src;
275 
276 	for (i = 0; i < (MD5_DIGEST_LENGTH / 4); i++)
277 		(void) snprintf((char *)dest + (i * 8), 9, "%.8x", *p++);
278 
279 	dest[HASHSTRLEN] = '\0';
280 }
281 
282 /*
283  * This open function assume that there is no simultaneous
284  * open/unlink operation is going on. The caller is supposed
285  * to ensure that both open in O_CREAT mode happen atomically.
286  * It returns the crflag as 1 if file is created else 0.
287  */
288 int
289 __pos4obj_open(const char *name, char *type, int oflag,
290 		mode_t mode, int *crflag)
291 {
292 	int fd;
293 	char *dfile;
294 
295 	errno = 0;
296 	*crflag = 0;
297 
298 	if ((dfile = __pos4obj_name(name, type)) == NULL) {
299 		return (-1);
300 	}
301 
302 	if (!(oflag & O_CREAT)) {
303 		if ((fd = __open_nc(dfile, oflag, mode)) == -1)
304 			__pos4obj_clean(dfile);
305 
306 		free(dfile);
307 		return (fd);
308 	}
309 
310 	/*
311 	 * We need to make sure that crflag is set iff we actually create
312 	 * the file.  We do this by or'ing in O_EXCL, and attempting an
313 	 * open.  If that fails with an EEXIST, and O_EXCL wasn't specified
314 	 * by the caller, then the file seems to exist;  we'll try an
315 	 * open with O_CREAT cleared.  If that succeeds, then the file
316 	 * did indeed exist.  If that fails with an ENOENT, however, the
317 	 * file was removed between the opens;  we need to take another
318 	 * lap.
319 	 */
320 	for (;;) {
321 		if ((fd = __open_nc(dfile, (oflag | O_EXCL), mode)) == -1) {
322 			if (errno == EEXIST && !(oflag & O_EXCL)) {
323 				fd = __open_nc(dfile, oflag & ~O_CREAT, mode);
324 
325 				if (fd == -1 && errno == ENOENT)
326 					continue;
327 				break;
328 			}
329 		} else {
330 			*crflag = 1;
331 		}
332 		break;
333 	}
334 
335 	free(dfile);
336 	return (fd);
337 }
338 
339 
340 int
341 __pos4obj_unlink(const char *name, const char *type)
342 {
343 	int	err;
344 	char	*dfile;
345 
346 	if ((dfile = __pos4obj_name(name, type)) == NULL) {
347 		return (-1);
348 	}
349 
350 	err = unlink(dfile);
351 
352 	__pos4obj_clean(dfile);
353 
354 	free(dfile);
355 
356 	return (err);
357 }
358 
359 /*
360  * This function opens the lock file for each named object
361  * the presence of this file in the file system is the lock
362  */
363 int
364 __pos4obj_lock(const char *name, const char *ltype)
365 {
366 	char	*dfile;
367 	int	fd;
368 	int	limit = 64;
369 
370 	if ((dfile = __pos4obj_name(name, ltype)) == NULL) {
371 		return (-1);
372 	}
373 
374 	while (limit-- > 0) {
375 		if ((fd = __open_nc(dfile, O_RDWR | O_CREAT | O_EXCL, 0666))
376 		    < 0) {
377 			if (errno != EEXIST)
378 				break;
379 			(void) sleep(1);
380 			continue;
381 		}
382 
383 		(void) __close_nc(fd);
384 		free(dfile);
385 		return (1);
386 	}
387 
388 	free(dfile);
389 	return (-1);
390 }
391 
392 /*
393  * Unlocks the file by unlinking it from the filesystem
394  */
395 int
396 __pos4obj_unlock(const char *path, const char *type)
397 {
398 	return (__pos4obj_unlink(path, type));
399 }
400 
401 /*
402  * Removes unused hash and type directories that may exist in specified path.
403  */
404 static void
405 __pos4obj_clean(char *path)
406 {
407 	char	*p;
408 	int	olderrno;
409 
410 	/*
411 	 * path is either
412 	 * 1) /<objroot>/<type><path>  or
413 	 * 2) /<objroot>/.<hash>/<type>/<path>
414 	 *
415 	 * In case 1, there is nothing to clean.
416 	 *
417 	 * Detect case 2 by looking for a '/' after /objroot/ and
418 	 * remove the two trailing directories, if empty.
419 	 */
420 	if (strchr(path + strlen(objroot), '/') == NULL)
421 		return;
422 
423 	/*
424 	 * Preserve errno across calls to rmdir. See block comment in
425 	 * __pos4obj_name() for explanation.
426 	 */
427 	olderrno = errno;
428 
429 	if ((p = strrchr(path, '/')) == NULL)
430 		return;
431 	*p = '\0';
432 
433 	(void) rmdir(path);
434 
435 	if ((p = strrchr(path, '/')) == NULL)
436 		return;
437 	*p = '\0';
438 
439 	(void) rmdir(path);
440 
441 	errno = olderrno;
442 }
443 
444 
445 /*
446  * Check that path starts with a /, does not contain a / within it
447  * and is not longer than PATH_MAX or NAME_MAX
448  */
449 int
450 __pos4obj_check(const char *path)
451 {
452 	long int	i;
453 
454 	/*
455 	 * This assumes that __pos4obj_check() is called before
456 	 * any of the other functions in this file
457 	 */
458 	if (name_max == 0 || name_max == -1) {
459 		name_max = pathconf(objroot, _PC_NAME_MAX);
460 		if (name_max == -1)
461 			return (-1);
462 	}
463 
464 	if (*path++ != '/') {
465 		errno = EINVAL;
466 		return (-1);
467 	}
468 
469 	for (i = 0; *path != '\0'; i++) {
470 		if (*path++ == '/') {
471 			errno = EINVAL;
472 			return (-1);
473 		}
474 	}
475 
476 	if (i > PATH_MAX || i > name_max) {
477 		errno = ENAMETOOLONG;
478 		return (-1);
479 	}
480 
481 	return (0);
482 }
483