xref: /dragonfly/crypto/openssh/sftp-realpath.c (revision 50a69bb5)
150a69bb5SSascha Wildner /*	$OpenBSD: sftp-realpath.c,v 1.2 2021/09/02 21:03:54 deraadt Exp $ */
20cbfa66cSDaniel Fojt /*
30cbfa66cSDaniel Fojt  * Copyright (c) 2003 Constantin S. Svintsoff <kostik@iclub.nsu.ru>
40cbfa66cSDaniel Fojt  *
50cbfa66cSDaniel Fojt  * Redistribution and use in source and binary forms, with or without
60cbfa66cSDaniel Fojt  * modification, are permitted provided that the following conditions
70cbfa66cSDaniel Fojt  * are met:
80cbfa66cSDaniel Fojt  * 1. Redistributions of source code must retain the above copyright
90cbfa66cSDaniel Fojt  *    notice, this list of conditions and the following disclaimer.
100cbfa66cSDaniel Fojt  * 2. Redistributions in binary form must reproduce the above copyright
110cbfa66cSDaniel Fojt  *    notice, this list of conditions and the following disclaimer in the
120cbfa66cSDaniel Fojt  *    documentation and/or other materials provided with the distribution.
130cbfa66cSDaniel Fojt  * 3. The names of the authors may not be used to endorse or promote
140cbfa66cSDaniel Fojt  *    products derived from this software without specific prior written
150cbfa66cSDaniel Fojt  *    permission.
160cbfa66cSDaniel Fojt  *
170cbfa66cSDaniel Fojt  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
180cbfa66cSDaniel Fojt  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
190cbfa66cSDaniel Fojt  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
200cbfa66cSDaniel Fojt  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
210cbfa66cSDaniel Fojt  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
220cbfa66cSDaniel Fojt  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
230cbfa66cSDaniel Fojt  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
240cbfa66cSDaniel Fojt  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
250cbfa66cSDaniel Fojt  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
260cbfa66cSDaniel Fojt  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
270cbfa66cSDaniel Fojt  * SUCH DAMAGE.
280cbfa66cSDaniel Fojt  */
290cbfa66cSDaniel Fojt 
300cbfa66cSDaniel Fojt #include "includes.h"
310cbfa66cSDaniel Fojt 
320cbfa66cSDaniel Fojt #include <sys/types.h>
330cbfa66cSDaniel Fojt #include <sys/stat.h>
340cbfa66cSDaniel Fojt 
350cbfa66cSDaniel Fojt #include <errno.h>
360cbfa66cSDaniel Fojt #include <stdlib.h>
370cbfa66cSDaniel Fojt #include <stddef.h>
380cbfa66cSDaniel Fojt #include <string.h>
390cbfa66cSDaniel Fojt #include <unistd.h>
400cbfa66cSDaniel Fojt #include <limits.h>
410cbfa66cSDaniel Fojt 
420cbfa66cSDaniel Fojt #ifndef SYMLOOP_MAX
430cbfa66cSDaniel Fojt # define SYMLOOP_MAX 32
440cbfa66cSDaniel Fojt #endif
450cbfa66cSDaniel Fojt 
460cbfa66cSDaniel Fojt /* XXX rewrite sftp-server to use POSIX realpath and remove this hack */
470cbfa66cSDaniel Fojt 
480cbfa66cSDaniel Fojt char *sftp_realpath(const char *path, char *resolved);
490cbfa66cSDaniel Fojt 
500cbfa66cSDaniel Fojt /*
510cbfa66cSDaniel Fojt  * char *realpath(const char *path, char resolved[PATH_MAX]);
520cbfa66cSDaniel Fojt  *
530cbfa66cSDaniel Fojt  * Find the real name of path, by removing all ".", ".." and symlink
540cbfa66cSDaniel Fojt  * components.  Returns (resolved) on success, or (NULL) on failure,
550cbfa66cSDaniel Fojt  * in which case the path which caused trouble is left in (resolved).
560cbfa66cSDaniel Fojt  */
570cbfa66cSDaniel Fojt char *
sftp_realpath(const char * path,char * resolved)580cbfa66cSDaniel Fojt sftp_realpath(const char *path, char *resolved)
590cbfa66cSDaniel Fojt {
600cbfa66cSDaniel Fojt 	struct stat sb;
610cbfa66cSDaniel Fojt 	char *p, *q, *s;
620cbfa66cSDaniel Fojt 	size_t left_len, resolved_len;
630cbfa66cSDaniel Fojt 	unsigned symlinks;
640cbfa66cSDaniel Fojt 	int serrno, slen, mem_allocated;
650cbfa66cSDaniel Fojt 	char left[PATH_MAX], next_token[PATH_MAX], symlink[PATH_MAX];
660cbfa66cSDaniel Fojt 
670cbfa66cSDaniel Fojt 	if (path[0] == '\0') {
680cbfa66cSDaniel Fojt 		errno = ENOENT;
690cbfa66cSDaniel Fojt 		return (NULL);
700cbfa66cSDaniel Fojt 	}
710cbfa66cSDaniel Fojt 
720cbfa66cSDaniel Fojt 	serrno = errno;
730cbfa66cSDaniel Fojt 
740cbfa66cSDaniel Fojt 	if (resolved == NULL) {
750cbfa66cSDaniel Fojt 		resolved = malloc(PATH_MAX);
760cbfa66cSDaniel Fojt 		if (resolved == NULL)
770cbfa66cSDaniel Fojt 			return (NULL);
780cbfa66cSDaniel Fojt 		mem_allocated = 1;
790cbfa66cSDaniel Fojt 	} else
800cbfa66cSDaniel Fojt 		mem_allocated = 0;
810cbfa66cSDaniel Fojt 
820cbfa66cSDaniel Fojt 	symlinks = 0;
830cbfa66cSDaniel Fojt 	if (path[0] == '/') {
840cbfa66cSDaniel Fojt 		resolved[0] = '/';
850cbfa66cSDaniel Fojt 		resolved[1] = '\0';
860cbfa66cSDaniel Fojt 		if (path[1] == '\0')
870cbfa66cSDaniel Fojt 			return (resolved);
880cbfa66cSDaniel Fojt 		resolved_len = 1;
890cbfa66cSDaniel Fojt 		left_len = strlcpy(left, path + 1, sizeof(left));
900cbfa66cSDaniel Fojt 	} else {
910cbfa66cSDaniel Fojt 		if (getcwd(resolved, PATH_MAX) == NULL) {
920cbfa66cSDaniel Fojt 			if (mem_allocated)
930cbfa66cSDaniel Fojt 				free(resolved);
940cbfa66cSDaniel Fojt 			else
950cbfa66cSDaniel Fojt 				strlcpy(resolved, ".", PATH_MAX);
960cbfa66cSDaniel Fojt 			return (NULL);
970cbfa66cSDaniel Fojt 		}
980cbfa66cSDaniel Fojt 		resolved_len = strlen(resolved);
990cbfa66cSDaniel Fojt 		left_len = strlcpy(left, path, sizeof(left));
1000cbfa66cSDaniel Fojt 	}
1010cbfa66cSDaniel Fojt 	if (left_len >= sizeof(left) || resolved_len >= PATH_MAX) {
1020cbfa66cSDaniel Fojt 		errno = ENAMETOOLONG;
1030cbfa66cSDaniel Fojt 		goto err;
1040cbfa66cSDaniel Fojt 	}
1050cbfa66cSDaniel Fojt 
1060cbfa66cSDaniel Fojt 	/*
1070cbfa66cSDaniel Fojt 	 * Iterate over path components in `left'.
1080cbfa66cSDaniel Fojt 	 */
1090cbfa66cSDaniel Fojt 	while (left_len != 0) {
1100cbfa66cSDaniel Fojt 		/*
1110cbfa66cSDaniel Fojt 		 * Extract the next path component and adjust `left'
1120cbfa66cSDaniel Fojt 		 * and its length.
1130cbfa66cSDaniel Fojt 		 */
1140cbfa66cSDaniel Fojt 		p = strchr(left, '/');
1150cbfa66cSDaniel Fojt 		s = p ? p : left + left_len;
1160cbfa66cSDaniel Fojt 		if (s - left >= (ptrdiff_t)sizeof(next_token)) {
1170cbfa66cSDaniel Fojt 			errno = ENAMETOOLONG;
1180cbfa66cSDaniel Fojt 			goto err;
1190cbfa66cSDaniel Fojt 		}
1200cbfa66cSDaniel Fojt 		memcpy(next_token, left, s - left);
1210cbfa66cSDaniel Fojt 		next_token[s - left] = '\0';
1220cbfa66cSDaniel Fojt 		left_len -= s - left;
1230cbfa66cSDaniel Fojt 		if (p != NULL)
1240cbfa66cSDaniel Fojt 			memmove(left, s + 1, left_len + 1);
1250cbfa66cSDaniel Fojt 		if (resolved[resolved_len - 1] != '/') {
1260cbfa66cSDaniel Fojt 			if (resolved_len + 1 >= PATH_MAX) {
1270cbfa66cSDaniel Fojt 				errno = ENAMETOOLONG;
1280cbfa66cSDaniel Fojt 				goto err;
1290cbfa66cSDaniel Fojt 			}
1300cbfa66cSDaniel Fojt 			resolved[resolved_len++] = '/';
1310cbfa66cSDaniel Fojt 			resolved[resolved_len] = '\0';
1320cbfa66cSDaniel Fojt 		}
1330cbfa66cSDaniel Fojt 		if (next_token[0] == '\0')
1340cbfa66cSDaniel Fojt 			continue;
1350cbfa66cSDaniel Fojt 		else if (strcmp(next_token, ".") == 0)
1360cbfa66cSDaniel Fojt 			continue;
1370cbfa66cSDaniel Fojt 		else if (strcmp(next_token, "..") == 0) {
1380cbfa66cSDaniel Fojt 			/*
1390cbfa66cSDaniel Fojt 			 * Strip the last path component except when we have
1400cbfa66cSDaniel Fojt 			 * single "/"
1410cbfa66cSDaniel Fojt 			 */
1420cbfa66cSDaniel Fojt 			if (resolved_len > 1) {
1430cbfa66cSDaniel Fojt 				resolved[resolved_len - 1] = '\0';
1440cbfa66cSDaniel Fojt 				q = strrchr(resolved, '/') + 1;
1450cbfa66cSDaniel Fojt 				*q = '\0';
1460cbfa66cSDaniel Fojt 				resolved_len = q - resolved;
1470cbfa66cSDaniel Fojt 			}
1480cbfa66cSDaniel Fojt 			continue;
1490cbfa66cSDaniel Fojt 		}
1500cbfa66cSDaniel Fojt 
1510cbfa66cSDaniel Fojt 		/*
1520cbfa66cSDaniel Fojt 		 * Append the next path component and lstat() it. If
1530cbfa66cSDaniel Fojt 		 * lstat() fails we still can return successfully if
1540cbfa66cSDaniel Fojt 		 * there are no more path components left.
1550cbfa66cSDaniel Fojt 		 */
1560cbfa66cSDaniel Fojt 		resolved_len = strlcat(resolved, next_token, PATH_MAX);
1570cbfa66cSDaniel Fojt 		if (resolved_len >= PATH_MAX) {
1580cbfa66cSDaniel Fojt 			errno = ENAMETOOLONG;
1590cbfa66cSDaniel Fojt 			goto err;
1600cbfa66cSDaniel Fojt 		}
1610cbfa66cSDaniel Fojt 		if (lstat(resolved, &sb) != 0) {
1620cbfa66cSDaniel Fojt 			if (errno == ENOENT && p == NULL) {
1630cbfa66cSDaniel Fojt 				errno = serrno;
1640cbfa66cSDaniel Fojt 				return (resolved);
1650cbfa66cSDaniel Fojt 			}
1660cbfa66cSDaniel Fojt 			goto err;
1670cbfa66cSDaniel Fojt 		}
1680cbfa66cSDaniel Fojt 		if (S_ISLNK(sb.st_mode)) {
1690cbfa66cSDaniel Fojt 			if (symlinks++ > SYMLOOP_MAX) {
1700cbfa66cSDaniel Fojt 				errno = ELOOP;
1710cbfa66cSDaniel Fojt 				goto err;
1720cbfa66cSDaniel Fojt 			}
1730cbfa66cSDaniel Fojt 			slen = readlink(resolved, symlink, sizeof(symlink) - 1);
1740cbfa66cSDaniel Fojt 			if (slen < 0)
1750cbfa66cSDaniel Fojt 				goto err;
1760cbfa66cSDaniel Fojt 			symlink[slen] = '\0';
1770cbfa66cSDaniel Fojt 			if (symlink[0] == '/') {
1780cbfa66cSDaniel Fojt 				resolved[1] = 0;
1790cbfa66cSDaniel Fojt 				resolved_len = 1;
1800cbfa66cSDaniel Fojt 			} else if (resolved_len > 1) {
1810cbfa66cSDaniel Fojt 				/* Strip the last path component. */
1820cbfa66cSDaniel Fojt 				resolved[resolved_len - 1] = '\0';
1830cbfa66cSDaniel Fojt 				q = strrchr(resolved, '/') + 1;
1840cbfa66cSDaniel Fojt 				*q = '\0';
1850cbfa66cSDaniel Fojt 				resolved_len = q - resolved;
1860cbfa66cSDaniel Fojt 			}
1870cbfa66cSDaniel Fojt 
1880cbfa66cSDaniel Fojt 			/*
1890cbfa66cSDaniel Fojt 			 * If there are any path components left, then
1900cbfa66cSDaniel Fojt 			 * append them to symlink. The result is placed
1910cbfa66cSDaniel Fojt 			 * in `left'.
1920cbfa66cSDaniel Fojt 			 */
1930cbfa66cSDaniel Fojt 			if (p != NULL) {
1940cbfa66cSDaniel Fojt 				if (symlink[slen - 1] != '/') {
1950cbfa66cSDaniel Fojt 					if (slen + 1 >=
1960cbfa66cSDaniel Fojt 					    (ptrdiff_t)sizeof(symlink)) {
1970cbfa66cSDaniel Fojt 						errno = ENAMETOOLONG;
1980cbfa66cSDaniel Fojt 						goto err;
1990cbfa66cSDaniel Fojt 					}
2000cbfa66cSDaniel Fojt 					symlink[slen] = '/';
2010cbfa66cSDaniel Fojt 					symlink[slen + 1] = 0;
2020cbfa66cSDaniel Fojt 				}
2030cbfa66cSDaniel Fojt 				left_len = strlcat(symlink, left, sizeof(symlink));
2040cbfa66cSDaniel Fojt 				if (left_len >= sizeof(symlink)) {
2050cbfa66cSDaniel Fojt 					errno = ENAMETOOLONG;
2060cbfa66cSDaniel Fojt 					goto err;
2070cbfa66cSDaniel Fojt 				}
2080cbfa66cSDaniel Fojt 			}
2090cbfa66cSDaniel Fojt 			left_len = strlcpy(left, symlink, sizeof(left));
2100cbfa66cSDaniel Fojt 		}
2110cbfa66cSDaniel Fojt 	}
2120cbfa66cSDaniel Fojt 
2130cbfa66cSDaniel Fojt 	/*
2140cbfa66cSDaniel Fojt 	 * Remove trailing slash except when the resolved pathname
2150cbfa66cSDaniel Fojt 	 * is a single "/".
2160cbfa66cSDaniel Fojt 	 */
2170cbfa66cSDaniel Fojt 	if (resolved_len > 1 && resolved[resolved_len - 1] == '/')
2180cbfa66cSDaniel Fojt 		resolved[resolved_len - 1] = '\0';
2190cbfa66cSDaniel Fojt 	return (resolved);
2200cbfa66cSDaniel Fojt 
2210cbfa66cSDaniel Fojt err:
2220cbfa66cSDaniel Fojt 	if (mem_allocated)
2230cbfa66cSDaniel Fojt 		free(resolved);
2240cbfa66cSDaniel Fojt 	return (NULL);
2250cbfa66cSDaniel Fojt }
226