xref: /netbsd-src/external/bsd/ntp/dist/libntp/ntp_realpath.c (revision 32d1c65c71fbdb65a012e8392a62a757dd6853e9)
1 /*	$NetBSD: ntp_realpath.c,v 1.2 2024/08/18 20:47:13 christos Exp $	*/
2 
3 /*
4  * ntp_realpath.c - get real path for a file
5  *	Juergen Perlinger (perlinger@ntp.org) for the NTP project.
6  *	Feb 11, 2014 for the NTP project.
7  *
8  * This is a butchered version of FreeBSD's implementation of 'realpath()',
9  * and the following copyright applies:
10  *----------------------------------------------------------------------
11  */
12 
13 /*-
14  * SPDX-License-Identifier: BSD-3-Clause
15  *
16  * Copyright (c) 2003 Constantin S. Svintsoff <kostik@iclub.nsu.ru>
17  *
18  * Redistribution and use in source and binary forms, with or without
19  * modification, are permitted provided that the following conditions
20  * are met:
21  * 1. Redistributions of source code must retain the above copyright
22  *    notice, this list of conditions and the following disclaimer.
23  * 2. Redistributions in binary form must reproduce the above copyright
24  *    notice, this list of conditions and the following disclaimer in the
25  *    documentation and/or other materials provided with the distribution.
26  * 3. The names of the authors may not be used to endorse or promote
27  *    products derived from this software without specific prior written
28  *    permission.
29  *
30  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
31  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
32  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
34  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
35  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
36  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
37  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
38  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
39  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
40  * SUCH DAMAGE.
41  */
42 
43 #ifdef HAVE_CONFIG_H
44 #include <config.h>
45 #endif
46 #include "ntp_stdlib.h"
47 
48 /* ================================================================== */
49 #if defined(SYS_WINNT)
50 /* ================================================================== */
51 
52 #include <stdlib.h>
53 
54 /* On Windows, we assume 2k for a file path is enough. */
55 #define NTP_PATH_MAX	2048
56 
57 static char *
58 realpath1(const char *path, char *resolved)
59 {
60 	/* Items in the device name space get passed back AS IS. Everything
61 	 * else is fed through '_fullpath()', which is probably the closest
62 	 * counterpart to what 'realpath()' is expected to do on Windows...
63 	 */
64 	char * retval = NULL;
65 
66 	if (!strncmp(path, "\\\\.\\", 4)) {
67 		if (strlcpy(resolved, path, NTP_PATH_MAX) >= NTP_PATH_MAX)
68 			errno = ENAMETOOLONG;
69 		else
70 			retval = resolved;
71 	} else if ((retval = _fullpath(resolved, path, NTP_PATH_MAX)) == NULL) {
72 		errno = ENAMETOOLONG;
73 	}
74 	return retval;
75 }
76 
77 /* ================================================================== */
78 #elif !defined(HAVE_FUNC_POSIX_REALPATH)
79 /* ================================================================== */
80 
81 #include <sys/stat.h>
82 #include <errno.h>
83 #include <stdlib.h>
84 #include <string.h>
85 #include <unistd.h>
86 #include <fcntl.h>
87 
88 /* The following definitions are to avoid system settings with excessive
89  * values for maxmimum path length and symlink chains/loops. Adjust with
90  * care, if that's ever needed: some buffers are on the stack!
91  */
92 #define NTP_PATH_MAX	1024
93 #define NTP_MAXSYMLINKS	16
94 
95 /*
96  * Find the real name of path, by removing all ".", ".." and symlink
97  * components.  Returns (resolved) on success, or (NULL) on failure,
98  * in which case the path which caused trouble is left in (resolved).
99  */
100 static char *
101 realpath1(const char *path, char *resolved)
102 {
103 	struct stat sb;
104 	char *p, *q;
105 	size_t left_len, resolved_len, next_token_len;
106 	unsigned symlinks;
107 	ssize_t slen;
108 	char left[NTP_PATH_MAX], next_token[NTP_PATH_MAX], link_tgt[NTP_PATH_MAX];
109 
110 	symlinks = 0;
111 	if (path[0] == '/') {
112 		resolved[0] = '/';
113 		resolved[1] = '\0';
114 		if (path[1] == '\0')
115 			return (resolved);
116 		resolved_len = 1;
117 		left_len = strlcpy(left, path + 1, sizeof(left));
118 	} else {
119 		if (getcwd(resolved, NTP_PATH_MAX) == NULL) {
120 			resolved[0] = '.';
121 			resolved[1] = '\0';
122 			return (NULL);
123 		}
124 		resolved_len = strlen(resolved);
125 		left_len = strlcpy(left, path, sizeof(left));
126 	}
127 	if (left_len >= sizeof(left) || resolved_len >= NTP_PATH_MAX) {
128 		errno = ENAMETOOLONG;
129 		return (NULL);
130 	}
131 
132 	/*
133 	 * Iterate over path components in `left'.
134 	 */
135 	while (left_len != 0) {
136 		/*
137 		 * Extract the next path component and adjust `left'
138 		 * and its length.
139 		 */
140 		p = strchr(left, '/');
141 
142 		next_token_len = p != NULL ? (size_t)(p - left) : left_len;
143 		memcpy(next_token, left, next_token_len);
144 		next_token[next_token_len] = '\0';
145 
146 		if (p != NULL) {
147 			left_len -= next_token_len + 1;
148 			memmove(left, p + 1, left_len + 1);
149 		} else {
150 			left[0] = '\0';
151 			left_len = 0;
152 		}
153 
154 		if (resolved[resolved_len - 1] != '/') {
155 			if (resolved_len + 1 >= NTP_PATH_MAX) {
156 				errno = ENAMETOOLONG;
157 				return (NULL);
158 			}
159 			resolved[resolved_len++] = '/';
160 			resolved[resolved_len] = '\0';
161 		}
162 		if ('\0' == next_token[0]) {
163 			/* Handle consequential slashes. */
164 			continue;
165 		} else if (strcmp(next_token, ".") == 0) {
166 			continue;
167 		} else if (strcmp(next_token, "..") == 0) {
168 			/*
169 			 * Strip the last path component except when we have
170 			 * single "/"
171 			 */
172 			if (resolved_len > 1) {
173 				resolved[resolved_len - 1] = '\0';
174 				q = strrchr(resolved, '/') + 1;
175 				*q = '\0';
176 				resolved_len = q - resolved;
177 			}
178 			continue;
179 		}
180 
181 		/*
182 		 * Append the next path component and lstat() it.
183 		 */
184 		resolved_len = strlcat(resolved, next_token, NTP_PATH_MAX);
185 		if (resolved_len >= NTP_PATH_MAX) {
186 			errno = ENAMETOOLONG;
187 			return (NULL);
188 		}
189 		if (lstat(resolved, &sb) != 0)
190 			return (NULL);
191 		if (S_ISLNK(sb.st_mode)) {
192 			if (++symlinks > NTP_MAXSYMLINKS) {
193 				errno = ELOOP;
194 				return (NULL);
195 			}
196 			slen = readlink(resolved, link_tgt, sizeof(link_tgt));
197 			if (slen <= 0 || slen >= (ssize_t)sizeof(link_tgt)) {
198 				if (slen < 0) {
199 					/* keep errno from readlink(2) call */
200 				} else if (slen == 0) {
201 					errno = ENOENT;
202 				} else {
203 					errno = ENAMETOOLONG;
204 				}
205 				return (NULL);
206 			}
207 			link_tgt[slen] = '\0';
208 			if (link_tgt[0] == '/') {
209 				resolved[1] = '\0';
210 				resolved_len = 1;
211 			} else {
212 				/* Strip the last path component. */
213 				q = strrchr(resolved, '/') + 1;
214 				*q = '\0';
215 				resolved_len = q - resolved;
216 			}
217 
218 			/*
219 			 * If there are any path components left, then
220 			 * append them to link_tgt. The result is placed
221 			 * in `left'.
222 			 */
223 			if (p != NULL) {
224 				if (link_tgt[slen - 1] != '/') {
225 					if (slen + 1 >= (ssize_t)sizeof(link_tgt)) {
226 						errno = ENAMETOOLONG;
227 						return (NULL);
228 					}
229 					link_tgt[slen] = '/';
230 					link_tgt[slen + 1] = 0;
231 				}
232 				left_len = strlcat(link_tgt, left,
233 						   sizeof(link_tgt));
234 				if (left_len >= sizeof(link_tgt)) {
235 					errno = ENAMETOOLONG;
236 					return (NULL);
237 				}
238 			}
239 			left_len = strlcpy(left, link_tgt, sizeof(left));
240 		} else if (!S_ISDIR(sb.st_mode) && p != NULL) {
241 			errno = ENOTDIR;
242 			return (NULL);
243 		}
244 	}
245 
246 	/*
247 	 * Remove trailing slash except when the resolved pathname
248 	 * is a single "/".
249 	 */
250 	if (resolved_len > 1 && resolved[resolved_len - 1] == '/')
251 		resolved[resolved_len - 1] = '\0';
252 	return (resolved);
253 }
254 
255 /* ================================================================== */
256 #endif /* !defined(SYS_WINNT) && !defined(HAVE_POSIX_REALPATH) */
257 /* ================================================================== */
258 
259 char *
260 ntp_realpath(const char * path)
261 {
262 #   if defined(HAVE_FUNC_POSIX_REALPATH)
263 
264 	return realpath(path, NULL);
265 
266 #   else
267 
268 	char *res = NULL, *m = NULL;
269 	if (path == NULL)
270 		errno = EINVAL;
271 	else if (path[0] == '\0')
272 		errno = ENOENT;
273 	else if ((m = malloc(NTP_PATH_MAX)) == NULL)
274 		errno = ENOMEM;	/* MSVCRT malloc does not set this... */
275 	else if ((res = realpath1(path, m)) == NULL)
276 		free(m);
277 	else
278 		res = realloc(res, strlen(res) + 1);
279 	return (res);
280 
281 #   endif
282 }
283