18791Sjkh/*
28791Sjkh * ntp_realpath.c - get real path for a file
38791Sjkh *	Juergen Perlinger (perlinger@ntp.org) for the NTP project.
48791Sjkh *	Feb 11, 2014 for the NTP project.
58791Sjkh *
68791Sjkh * This is a butchered version of FreeBSD's implementation of 'realpath()',
720233Sjkh * and the following copyright applies:
88791Sjkh *----------------------------------------------------------------------
98791Sjkh */
108791Sjkh
118791Sjkh/*-
128791Sjkh * SPDX-License-Identifier: BSD-3-Clause
138791Sjkh *
148791Sjkh * Copyright (c) 2003 Constantin S. Svintsoff <kostik@iclub.nsu.ru>
158791Sjkh *
168881Srgrimes * Redistribution and use in source and binary forms, with or without
178881Srgrimes * modification, are permitted provided that the following conditions
188791Sjkh * are met:
1920231Sjkh * 1. Redistributions of source code must retain the above copyright
208791Sjkh *    notice, this list of conditions and the following disclaimer.
218791Sjkh * 2. Redistributions in binary form must reproduce the above copyright
228791Sjkh *    notice, this list of conditions and the following disclaimer in the
238791Sjkh *    documentation and/or other materials provided with the distribution.
248791Sjkh * 3. The names of the authors may not be used to endorse or promote
258791Sjkh *    products derived from this software without specific prior written
268791Sjkh *    permission.
278791Sjkh *
288791Sjkh * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
298791Sjkh * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
308791Sjkh * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
318791Sjkh * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
328791Sjkh * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
338791Sjkh * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
348791Sjkh * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
358791Sjkh * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
368791Sjkh * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
378791Sjkh * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
388803Sjkh * SUCH DAMAGE.
398791Sjkh */
408791Sjkh
4112661Speter#ifdef HAVE_CONFIG_H
428810Sjkh#include <config.h>
4312661Speter#endif
448837Sjkh#include "ntp_stdlib.h"
458791Sjkh
469202Srgrimes/* ================================================================== */
479202Srgrimes#if defined(SYS_WINNT)
488791Sjkh/* ================================================================== */
4920233Sjkh
5020233Sjkh#include <stdlib.h>
518791Sjkh
528791Sjkh/* On Windows, we assume 2k for a file path is enough. */
538791Sjkh#define NTP_PATH_MAX	2048
548791Sjkh
558791Sjkhstatic char *
5620233Sjkhrealpath1(const char *path, char *resolved)
578791Sjkh{
5812661Speter	/* Items in the device name space get passed back AS IS. Everything
598791Sjkh	 * else is fed through '_fullpath()', which is probably the closest
608791Sjkh	 * counterpart to what 'realpath()' is expected to do on Windows...
6112661Speter	 */
6212661Speter	char * retval = NULL;
6312661Speter
6412661Speter	if (!strncmp(path, "\\\\.\\", 4)) {
6520233Sjkh		if (strlcpy(resolved, path, NTP_PATH_MAX) >= NTP_PATH_MAX)
6620233Sjkh			errno = ENAMETOOLONG;
6720233Sjkh		else
6820233Sjkh			retval = resolved;
6912661Speter	} else if ((retval = _fullpath(resolved, path, NTP_PATH_MAX)) == NULL) {
7016330Sjkh		errno = ENAMETOOLONG;
7120233Sjkh	}
7216330Sjkh	return retval;
7316330Sjkh}
748791Sjkh
7516330Sjkh/* ================================================================== */
7616330Sjkh#elif !defined(HAVE_FUNC_POSIX_REALPATH)
7716330Sjkh/* ================================================================== */
7816330Sjkh
7916330Sjkh#include <sys/stat.h>
8016330Sjkh#include <errno.h>
818837Sjkh#include <stdlib.h>
8217404Sjkh#include <string.h>
8316330Sjkh#include <unistd.h>
8416330Sjkh#include <fcntl.h>
8516330Sjkh
8616330Sjkh/* The following definitions are to avoid system settings with excessive
8716330Sjkh * values for maxmimum path length and symlink chains/loops. Adjust with
8816330Sjkh * care, if that's ever needed: some buffers are on the stack!
8916330Sjkh */
9016330Sjkh#define NTP_PATH_MAX	1024
9116330Sjkh#define NTP_MAXSYMLINKS	16
9216330Sjkh
9316330Sjkh/*
9416330Sjkh * Find the real name of path, by removing all ".", ".." and symlink
9516330Sjkh * components.  Returns (resolved) on success, or (NULL) on failure,
9616330Sjkh * in which case the path which caused trouble is left in (resolved).
9716330Sjkh */
9820231Sjkhstatic char *
9920231Sjkhrealpath1(const char *path, char *resolved)
10020233Sjkh{
10120233Sjkh	struct stat sb;
10220231Sjkh	char *p, *q;
10316330Sjkh	size_t left_len, resolved_len, next_token_len;
10416330Sjkh	unsigned symlinks;
10516330Sjkh	ssize_t slen;
10616330Sjkh	char left[NTP_PATH_MAX], next_token[NTP_PATH_MAX], link_tgt[NTP_PATH_MAX];
1078837Sjkh
1088791Sjkh	symlinks = 0;
1098791Sjkh	if (path[0] == '/') {
11012661Speter		resolved[0] = '/';
11112661Speter		resolved[1] = '\0';
11210882Speter		if (path[1] == '\0')
11312661Speter			return (resolved);
11416330Sjkh		resolved_len = 1;
11510882Speter		left_len = strlcpy(left, path + 1, sizeof(left));
1168791Sjkh	} else {
11720208Sjkh		if (getcwd(resolved, NTP_PATH_MAX) == NULL) {
11816330Sjkh			resolved[0] = '.';
11910882Speter			resolved[1] = '\0';
12012661Speter			return (NULL);
12116330Sjkh		}
12210882Speter		resolved_len = strlen(resolved);
12310882Speter		left_len = strlcpy(left, path, sizeof(left));
1248791Sjkh	}
12512661Speter	if (left_len >= sizeof(left) || resolved_len >= NTP_PATH_MAX) {
12612661Speter		errno = ENAMETOOLONG;
12712661Speter		return (NULL);
12812661Speter	}
12912661Speter
13012661Speter	/*
13112661Speter	 * Iterate over path components in `left'.
1328791Sjkh	 */
13312661Speter	while (left_len != 0) {
13420208Sjkh		/*
13520208Sjkh		 * Extract the next path component and adjust `left'
1368791Sjkh		 * and its length.
1378791Sjkh		 */
1388791Sjkh		p = strchr(left, '/');
1398791Sjkh
1408791Sjkh		next_token_len = p != NULL ? (size_t)(p - left) : left_len;
1418791Sjkh		memcpy(next_token, left, next_token_len);
1428791Sjkh		next_token[next_token_len] = '\0';
1438791Sjkh
1448791Sjkh		if (p != NULL) {
14512661Speter			left_len -= next_token_len + 1;
1468791Sjkh			memmove(left, p + 1, left_len + 1);
1478791Sjkh		} else {
14820208Sjkh			left[0] = '\0';
14920208Sjkh			left_len = 0;
15016330Sjkh		}
15116330Sjkh
1528791Sjkh		if (resolved[resolved_len - 1] != '/') {
15312661Speter			if (resolved_len + 1 >= NTP_PATH_MAX) {
1548791Sjkh				errno = ENAMETOOLONG;
15512661Speter				return (NULL);
15612661Speter			}
1578791Sjkh			resolved[resolved_len++] = '/';
1588791Sjkh			resolved[resolved_len] = '\0';
15920208Sjkh		}
1608791Sjkh		if ('\0' == next_token[0]) {
16115355Sjkh			/* Handle consequential slashes. */
1628791Sjkh			continue;
16312661Speter		} else if (strcmp(next_token, ".") == 0) {
16412661Speter			continue;
16512661Speter		} else if (strcmp(next_token, "..") == 0) {
1668810Sjkh			/*
16712661Speter			 * Strip the last path component except when we have
1688791Sjkh			 * single "/"
16920233Sjkh			 */
17020233Sjkh			if (resolved_len > 1) {
17120233Sjkh				resolved[resolved_len - 1] = '\0';
17220233Sjkh				q = strrchr(resolved, '/') + 1;
1739202Srgrimes				*q = '\0';
17420208Sjkh				resolved_len = q - resolved;
1758810Sjkh			}
1768791Sjkh			continue;
1778810Sjkh		}
1789202Srgrimes
1798810Sjkh		/*
1808810Sjkh		 * Append the next path component and lstat() it.
18112661Speter		 */
1828810Sjkh		resolved_len = strlcat(resolved, next_token, NTP_PATH_MAX);
1838810Sjkh		if (resolved_len >= NTP_PATH_MAX) {
18415439Sjkh			errno = ENAMETOOLONG;
1859202Srgrimes			return (NULL);
1868810Sjkh		}
1879202Srgrimes		if (lstat(resolved, &sb) != 0)
18817005Sjkh			return (NULL);
18917005Sjkh		if (S_ISLNK(sb.st_mode)) {
19017005Sjkh			if (++symlinks > NTP_MAXSYMLINKS) {
1919202Srgrimes				errno = ELOOP;
19217404Sjkh				return (NULL);
19312661Speter			}
19412661Speter			slen = readlink(resolved, link_tgt, sizeof(link_tgt));
1959202Srgrimes			if (slen <= 0 || slen >= (ssize_t)sizeof(link_tgt)) {
19612661Speter				if (slen < 0) {
19712661Speter					/* keep errno from readlink(2) call */
19812661Speter				} else if (slen == 0) {
19912661Speter					errno = ENOENT;
20012661Speter				} else {
20120233Sjkh					errno = ENAMETOOLONG;
2029202Srgrimes				}
20320233Sjkh				return (NULL);
20412661Speter			}
20512661Speter			link_tgt[slen] = '\0';
20620233Sjkh			if (link_tgt[0] == '/') {
2079202Srgrimes				resolved[1] = '\0';
2089202Srgrimes				resolved_len = 1;
2099202Srgrimes			} else {
2109202Srgrimes				/* Strip the last path component. */
2119202Srgrimes				q = strrchr(resolved, '/') + 1;
2129202Srgrimes				*q = '\0';
21317007Sjkh				resolved_len = q - resolved;
21417007Sjkh			}
21517007Sjkh
21617007Sjkh			/*
2179202Srgrimes			 * If there are any path components left, then
2189202Srgrimes			 * append them to link_tgt. The result is placed
2199202Srgrimes			 * in `left'.
2209202Srgrimes			 */
2219202Srgrimes			if (p != NULL) {
2229202Srgrimes				if (link_tgt[slen - 1] != '/') {
2238828Sjkh					if (slen + 1 >= (ssize_t)sizeof(link_tgt)) {
22417007Sjkh						errno = ENAMETOOLONG;
22517007Sjkh						return (NULL);
22617007Sjkh					}
22717007Sjkh					link_tgt[slen] = '/';
2288828Sjkh					link_tgt[slen + 1] = 0;
22912661Speter				}
2308828Sjkh				left_len = strlcat(link_tgt, left,
2318828Sjkh						   sizeof(link_tgt));
23217007Sjkh				if (left_len >= sizeof(link_tgt)) {
23317007Sjkh					errno = ENAMETOOLONG;
23417007Sjkh					return (NULL);
23517007Sjkh				}
2368810Sjkh			}
2378810Sjkh			left_len = strlcpy(left, link_tgt, sizeof(left));
2389202Srgrimes		} else if (!S_ISDIR(sb.st_mode) && p != NULL) {
2398810Sjkh			errno = ENOTDIR;
2408810Sjkh			return (NULL);
2419202Srgrimes		}
2428810Sjkh	}
2439202Srgrimes
2448810Sjkh	/*
2459202Srgrimes	 * Remove trailing slash except when the resolved pathname
24617007Sjkh	 * is a single "/".
2479202Srgrimes	 */
2489202Srgrimes	if (resolved_len > 1 && resolved[resolved_len - 1] == '/')
2499202Srgrimes		resolved[resolved_len - 1] = '\0';
25015439Sjkh	return (resolved);
25115439Sjkh}
25212661Speter
25312661Speter/* ================================================================== */
25412661Speter#endif /* !defined(SYS_WINNT) && !defined(HAVE_POSIX_REALPATH) */
25512661Speter/* ================================================================== */
25612661Speter
25712661Speterchar *
25812661Speterntp_realpath(const char * path)
25912661Speter{
26012661Speter#   if defined(HAVE_FUNC_POSIX_REALPATH)
26112661Speter
26212661Speter	return realpath(path, NULL);
26312661Speter
26412661Speter#   else
26512661Speter
26612661Speter	char *res = NULL, *m = NULL;
26712661Speter	if (path == NULL)
26812661Speter		errno = EINVAL;
26912661Speter	else if (path[0] == '\0')
27012661Speter		errno = ENOENT;
27112661Speter	else if ((m = malloc(NTP_PATH_MAX)) == NULL)
27212661Speter		errno = ENOMEM;	/* MSVCRT malloc does not set this... */
27312661Speter	else if ((res = realpath1(path, m)) == NULL)
27412661Speter		free(m);
27512661Speter	else
2768810Sjkh		res = realloc(res, strlen(res) + 1);
2778810Sjkh	return (res);
27812661Speter
27917007Sjkh#   endif
28012661Speter}
28112661Speter