xref: /netbsd-src/external/bsd/ntp/dist/libntp/ntp_realpath.c (revision eabc0478de71e4e011a5b4e0392741e01d491794)
1*eabc0478Schristos /*	$NetBSD: ntp_realpath.c,v 1.2 2024/08/18 20:47:13 christos Exp $	*/
2897be3a4Schristos 
3897be3a4Schristos /*
4897be3a4Schristos  * ntp_realpath.c - get real path for a file
5897be3a4Schristos  *	Juergen Perlinger (perlinger@ntp.org) for the NTP project.
6897be3a4Schristos  *	Feb 11, 2014 for the NTP project.
7897be3a4Schristos  *
8897be3a4Schristos  * This is a butchered version of FreeBSD's implementation of 'realpath()',
9897be3a4Schristos  * and the following copyright applies:
10897be3a4Schristos  *----------------------------------------------------------------------
11897be3a4Schristos  */
12897be3a4Schristos 
13897be3a4Schristos /*-
14897be3a4Schristos  * SPDX-License-Identifier: BSD-3-Clause
15897be3a4Schristos  *
16897be3a4Schristos  * Copyright (c) 2003 Constantin S. Svintsoff <kostik@iclub.nsu.ru>
17897be3a4Schristos  *
18897be3a4Schristos  * Redistribution and use in source and binary forms, with or without
19897be3a4Schristos  * modification, are permitted provided that the following conditions
20897be3a4Schristos  * are met:
21897be3a4Schristos  * 1. Redistributions of source code must retain the above copyright
22897be3a4Schristos  *    notice, this list of conditions and the following disclaimer.
23897be3a4Schristos  * 2. Redistributions in binary form must reproduce the above copyright
24897be3a4Schristos  *    notice, this list of conditions and the following disclaimer in the
25897be3a4Schristos  *    documentation and/or other materials provided with the distribution.
26897be3a4Schristos  * 3. The names of the authors may not be used to endorse or promote
27897be3a4Schristos  *    products derived from this software without specific prior written
28897be3a4Schristos  *    permission.
29897be3a4Schristos  *
30897be3a4Schristos  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
31897be3a4Schristos  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
32897be3a4Schristos  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33897be3a4Schristos  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
34897be3a4Schristos  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
35897be3a4Schristos  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
36897be3a4Schristos  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
37897be3a4Schristos  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
38897be3a4Schristos  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
39897be3a4Schristos  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
40897be3a4Schristos  * SUCH DAMAGE.
41897be3a4Schristos  */
42897be3a4Schristos 
43897be3a4Schristos #ifdef HAVE_CONFIG_H
44897be3a4Schristos #include <config.h>
45897be3a4Schristos #endif
46897be3a4Schristos #include "ntp_stdlib.h"
47897be3a4Schristos 
48897be3a4Schristos /* ================================================================== */
49897be3a4Schristos #if defined(SYS_WINNT)
50897be3a4Schristos /* ================================================================== */
51897be3a4Schristos 
52897be3a4Schristos #include <stdlib.h>
53897be3a4Schristos 
54897be3a4Schristos /* On Windows, we assume 2k for a file path is enough. */
55897be3a4Schristos #define NTP_PATH_MAX	2048
56897be3a4Schristos 
57897be3a4Schristos static char *
58897be3a4Schristos realpath1(const char *path, char *resolved)
59897be3a4Schristos {
60897be3a4Schristos 	/* Items in the device name space get passed back AS IS. Everything
61897be3a4Schristos 	 * else is fed through '_fullpath()', which is probably the closest
62897be3a4Schristos 	 * counterpart to what 'realpath()' is expected to do on Windows...
63897be3a4Schristos 	 */
64897be3a4Schristos 	char * retval = NULL;
65897be3a4Schristos 
66897be3a4Schristos 	if (!strncmp(path, "\\\\.\\", 4)) {
67897be3a4Schristos 		if (strlcpy(resolved, path, NTP_PATH_MAX) >= NTP_PATH_MAX)
68897be3a4Schristos 			errno = ENAMETOOLONG;
69897be3a4Schristos 		else
70897be3a4Schristos 			retval = resolved;
71897be3a4Schristos 	} else if ((retval = _fullpath(resolved, path, NTP_PATH_MAX)) == NULL) {
72897be3a4Schristos 		errno = ENAMETOOLONG;
73897be3a4Schristos 	}
74897be3a4Schristos 	return retval;
75897be3a4Schristos }
76897be3a4Schristos 
77897be3a4Schristos /* ================================================================== */
78897be3a4Schristos #elif !defined(HAVE_FUNC_POSIX_REALPATH)
79897be3a4Schristos /* ================================================================== */
80897be3a4Schristos 
81897be3a4Schristos #include <sys/stat.h>
82897be3a4Schristos #include <errno.h>
83897be3a4Schristos #include <stdlib.h>
84897be3a4Schristos #include <string.h>
85897be3a4Schristos #include <unistd.h>
86897be3a4Schristos #include <fcntl.h>
87897be3a4Schristos 
88897be3a4Schristos /* The following definitions are to avoid system settings with excessive
89897be3a4Schristos  * values for maxmimum path length and symlink chains/loops. Adjust with
90897be3a4Schristos  * care, if that's ever needed: some buffers are on the stack!
91897be3a4Schristos  */
92897be3a4Schristos #define NTP_PATH_MAX	1024
93897be3a4Schristos #define NTP_MAXSYMLINKS	16
94897be3a4Schristos 
95897be3a4Schristos /*
96897be3a4Schristos  * Find the real name of path, by removing all ".", ".." and symlink
97897be3a4Schristos  * components.  Returns (resolved) on success, or (NULL) on failure,
98897be3a4Schristos  * in which case the path which caused trouble is left in (resolved).
99897be3a4Schristos  */
100897be3a4Schristos static char *
101897be3a4Schristos realpath1(const char *path, char *resolved)
102897be3a4Schristos {
103897be3a4Schristos 	struct stat sb;
104897be3a4Schristos 	char *p, *q;
105897be3a4Schristos 	size_t left_len, resolved_len, next_token_len;
106897be3a4Schristos 	unsigned symlinks;
107897be3a4Schristos 	ssize_t slen;
108897be3a4Schristos 	char left[NTP_PATH_MAX], next_token[NTP_PATH_MAX], link_tgt[NTP_PATH_MAX];
109897be3a4Schristos 
110897be3a4Schristos 	symlinks = 0;
111897be3a4Schristos 	if (path[0] == '/') {
112897be3a4Schristos 		resolved[0] = '/';
113897be3a4Schristos 		resolved[1] = '\0';
114897be3a4Schristos 		if (path[1] == '\0')
115897be3a4Schristos 			return (resolved);
116897be3a4Schristos 		resolved_len = 1;
117897be3a4Schristos 		left_len = strlcpy(left, path + 1, sizeof(left));
118897be3a4Schristos 	} else {
119897be3a4Schristos 		if (getcwd(resolved, NTP_PATH_MAX) == NULL) {
120897be3a4Schristos 			resolved[0] = '.';
121897be3a4Schristos 			resolved[1] = '\0';
122897be3a4Schristos 			return (NULL);
123897be3a4Schristos 		}
124897be3a4Schristos 		resolved_len = strlen(resolved);
125897be3a4Schristos 		left_len = strlcpy(left, path, sizeof(left));
126897be3a4Schristos 	}
127897be3a4Schristos 	if (left_len >= sizeof(left) || resolved_len >= NTP_PATH_MAX) {
128897be3a4Schristos 		errno = ENAMETOOLONG;
129897be3a4Schristos 		return (NULL);
130897be3a4Schristos 	}
131897be3a4Schristos 
132897be3a4Schristos 	/*
133897be3a4Schristos 	 * Iterate over path components in `left'.
134897be3a4Schristos 	 */
135897be3a4Schristos 	while (left_len != 0) {
136897be3a4Schristos 		/*
137897be3a4Schristos 		 * Extract the next path component and adjust `left'
138897be3a4Schristos 		 * and its length.
139897be3a4Schristos 		 */
140897be3a4Schristos 		p = strchr(left, '/');
141897be3a4Schristos 
142897be3a4Schristos 		next_token_len = p != NULL ? (size_t)(p - left) : left_len;
143897be3a4Schristos 		memcpy(next_token, left, next_token_len);
144897be3a4Schristos 		next_token[next_token_len] = '\0';
145897be3a4Schristos 
146897be3a4Schristos 		if (p != NULL) {
147897be3a4Schristos 			left_len -= next_token_len + 1;
148897be3a4Schristos 			memmove(left, p + 1, left_len + 1);
149897be3a4Schristos 		} else {
150897be3a4Schristos 			left[0] = '\0';
151897be3a4Schristos 			left_len = 0;
152897be3a4Schristos 		}
153897be3a4Schristos 
154897be3a4Schristos 		if (resolved[resolved_len - 1] != '/') {
155897be3a4Schristos 			if (resolved_len + 1 >= NTP_PATH_MAX) {
156897be3a4Schristos 				errno = ENAMETOOLONG;
157897be3a4Schristos 				return (NULL);
158897be3a4Schristos 			}
159897be3a4Schristos 			resolved[resolved_len++] = '/';
160897be3a4Schristos 			resolved[resolved_len] = '\0';
161897be3a4Schristos 		}
162897be3a4Schristos 		if ('\0' == next_token[0]) {
163897be3a4Schristos 			/* Handle consequential slashes. */
164897be3a4Schristos 			continue;
165897be3a4Schristos 		} else if (strcmp(next_token, ".") == 0) {
166897be3a4Schristos 			continue;
167897be3a4Schristos 		} else if (strcmp(next_token, "..") == 0) {
168897be3a4Schristos 			/*
169897be3a4Schristos 			 * Strip the last path component except when we have
170897be3a4Schristos 			 * single "/"
171897be3a4Schristos 			 */
172897be3a4Schristos 			if (resolved_len > 1) {
173897be3a4Schristos 				resolved[resolved_len - 1] = '\0';
174897be3a4Schristos 				q = strrchr(resolved, '/') + 1;
175897be3a4Schristos 				*q = '\0';
176897be3a4Schristos 				resolved_len = q - resolved;
177897be3a4Schristos 			}
178897be3a4Schristos 			continue;
179897be3a4Schristos 		}
180897be3a4Schristos 
181897be3a4Schristos 		/*
182897be3a4Schristos 		 * Append the next path component and lstat() it.
183897be3a4Schristos 		 */
184897be3a4Schristos 		resolved_len = strlcat(resolved, next_token, NTP_PATH_MAX);
185897be3a4Schristos 		if (resolved_len >= NTP_PATH_MAX) {
186897be3a4Schristos 			errno = ENAMETOOLONG;
187897be3a4Schristos 			return (NULL);
188897be3a4Schristos 		}
189897be3a4Schristos 		if (lstat(resolved, &sb) != 0)
190897be3a4Schristos 			return (NULL);
191897be3a4Schristos 		if (S_ISLNK(sb.st_mode)) {
192897be3a4Schristos 			if (++symlinks > NTP_MAXSYMLINKS) {
193897be3a4Schristos 				errno = ELOOP;
194897be3a4Schristos 				return (NULL);
195897be3a4Schristos 			}
196897be3a4Schristos 			slen = readlink(resolved, link_tgt, sizeof(link_tgt));
197897be3a4Schristos 			if (slen <= 0 || slen >= (ssize_t)sizeof(link_tgt)) {
198897be3a4Schristos 				if (slen < 0) {
199897be3a4Schristos 					/* keep errno from readlink(2) call */
200897be3a4Schristos 				} else if (slen == 0) {
201897be3a4Schristos 					errno = ENOENT;
202897be3a4Schristos 				} else {
203897be3a4Schristos 					errno = ENAMETOOLONG;
204897be3a4Schristos 				}
205897be3a4Schristos 				return (NULL);
206897be3a4Schristos 			}
207897be3a4Schristos 			link_tgt[slen] = '\0';
208897be3a4Schristos 			if (link_tgt[0] == '/') {
209897be3a4Schristos 				resolved[1] = '\0';
210897be3a4Schristos 				resolved_len = 1;
211897be3a4Schristos 			} else {
212897be3a4Schristos 				/* Strip the last path component. */
213897be3a4Schristos 				q = strrchr(resolved, '/') + 1;
214897be3a4Schristos 				*q = '\0';
215897be3a4Schristos 				resolved_len = q - resolved;
216897be3a4Schristos 			}
217897be3a4Schristos 
218897be3a4Schristos 			/*
219897be3a4Schristos 			 * If there are any path components left, then
220897be3a4Schristos 			 * append them to link_tgt. The result is placed
221897be3a4Schristos 			 * in `left'.
222897be3a4Schristos 			 */
223897be3a4Schristos 			if (p != NULL) {
224897be3a4Schristos 				if (link_tgt[slen - 1] != '/') {
225897be3a4Schristos 					if (slen + 1 >= (ssize_t)sizeof(link_tgt)) {
226897be3a4Schristos 						errno = ENAMETOOLONG;
227897be3a4Schristos 						return (NULL);
228897be3a4Schristos 					}
229897be3a4Schristos 					link_tgt[slen] = '/';
230897be3a4Schristos 					link_tgt[slen + 1] = 0;
231897be3a4Schristos 				}
232897be3a4Schristos 				left_len = strlcat(link_tgt, left,
233897be3a4Schristos 						   sizeof(link_tgt));
234897be3a4Schristos 				if (left_len >= sizeof(link_tgt)) {
235897be3a4Schristos 					errno = ENAMETOOLONG;
236897be3a4Schristos 					return (NULL);
237897be3a4Schristos 				}
238897be3a4Schristos 			}
239897be3a4Schristos 			left_len = strlcpy(left, link_tgt, sizeof(left));
240897be3a4Schristos 		} else if (!S_ISDIR(sb.st_mode) && p != NULL) {
241897be3a4Schristos 			errno = ENOTDIR;
242897be3a4Schristos 			return (NULL);
243897be3a4Schristos 		}
244897be3a4Schristos 	}
245897be3a4Schristos 
246897be3a4Schristos 	/*
247897be3a4Schristos 	 * Remove trailing slash except when the resolved pathname
248897be3a4Schristos 	 * is a single "/".
249897be3a4Schristos 	 */
250897be3a4Schristos 	if (resolved_len > 1 && resolved[resolved_len - 1] == '/')
251897be3a4Schristos 		resolved[resolved_len - 1] = '\0';
252897be3a4Schristos 	return (resolved);
253897be3a4Schristos }
254897be3a4Schristos 
255897be3a4Schristos /* ================================================================== */
256897be3a4Schristos #endif /* !defined(SYS_WINNT) && !defined(HAVE_POSIX_REALPATH) */
257897be3a4Schristos /* ================================================================== */
258897be3a4Schristos 
259897be3a4Schristos char *
260897be3a4Schristos ntp_realpath(const char * path)
261897be3a4Schristos {
262897be3a4Schristos #   if defined(HAVE_FUNC_POSIX_REALPATH)
263897be3a4Schristos 
264897be3a4Schristos 	return realpath(path, NULL);
265897be3a4Schristos 
266897be3a4Schristos #   else
267897be3a4Schristos 
268897be3a4Schristos 	char *res = NULL, *m = NULL;
269897be3a4Schristos 	if (path == NULL)
270897be3a4Schristos 		errno = EINVAL;
271897be3a4Schristos 	else if (path[0] == '\0')
272897be3a4Schristos 		errno = ENOENT;
273897be3a4Schristos 	else if ((m = malloc(NTP_PATH_MAX)) == NULL)
274897be3a4Schristos 		errno = ENOMEM;	/* MSVCRT malloc does not set this... */
275897be3a4Schristos 	else if ((res = realpath1(path, m)) == NULL)
276897be3a4Schristos 		free(m);
277897be3a4Schristos 	else
278897be3a4Schristos 		res = realloc(res, strlen(res) + 1);
279897be3a4Schristos 	return (res);
280897be3a4Schristos 
281897be3a4Schristos #   endif
282897be3a4Schristos }
283