1127668Sbms/* $OpenBSD: sftp-realpath.c,v 1.2 2021/09/02 21:03:54 deraadt Exp $ */ 2127668Sbms/* 3127668Sbms * Copyright (c) 2003 Constantin S. Svintsoff <kostik@iclub.nsu.ru> 4127668Sbms * 5127668Sbms * Redistribution and use in source and binary forms, with or without 6127668Sbms * modification, are permitted provided that the following conditions 7127668Sbms * are met: 8127668Sbms * 1. Redistributions of source code must retain the above copyright 9127668Sbms * notice, this list of conditions and the following disclaimer. 10127668Sbms * 2. Redistributions in binary form must reproduce the above copyright 11127668Sbms * notice, this list of conditions and the following disclaimer in the 12127668Sbms * documentation and/or other materials provided with the distribution. 13127668Sbms * 3. The names of the authors may not be used to endorse or promote 14127668Sbms * products derived from this software without specific prior written 15127668Sbms * permission. 16127668Sbms * 17127668Sbms * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 18190207Srpaulo * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19127668Sbms * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20127668Sbms * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 21127668Sbms * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22127668Sbms * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23127668Sbms * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24127668Sbms * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25127668Sbms * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26127668Sbms * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27127668Sbms * SUCH DAMAGE. 28127668Sbms */ 29127668Sbms#include "includes.h" 30127668Sbms__RCSID("$NetBSD: sftp-realpath.c,v 1.3 2021/09/27 17:03:13 christos Exp $"); 31127668Sbms 32127668Sbms#include <sys/types.h> 33127668Sbms#include <sys/stat.h> 34127668Sbms 35127668Sbms#include <errno.h> 36127668Sbms#include <stdlib.h> 37147899Ssam#include <stddef.h> 38127668Sbms#include <string.h> 39127668Sbms#include <unistd.h> 40127668Sbms#include <limits.h> 41127668Sbms 42127668Sbms#ifndef SYMLOOP_MAX 43127668Sbms# define SYMLOOP_MAX 32 44127668Sbms#endif 45127668Sbms 46127668Sbms/* XXX rewrite sftp-server to use POSIX realpath and remove this hack */ 47127668Sbms 48127668Sbmschar *sftp_realpath(const char *path, char *resolved); 49127668Sbms 50127668Sbms/* 51127668Sbms * char *realpath(const char *path, char resolved[PATH_MAX]); 52127668Sbms * 53127668Sbms * Find the real name of path, by removing all ".", ".." and symlink 54127668Sbms * components. Returns (resolved) on success, or (NULL) on failure, 55127668Sbms * in which case the path which caused trouble is left in (resolved). 56147899Ssam */ 57147899Ssamchar * 58147899Ssamsftp_realpath(const char *path, char *resolved) 59147899Ssam{ 60147899Ssam struct stat sb; 61147899Ssam char *p, *q, *s; 62147899Ssam size_t left_len, resolved_len; 63147899Ssam unsigned symlinks; 64147899Ssam int serrno, slen, mem_allocated; 65147899Ssam char left[PATH_MAX], next_token[PATH_MAX], symlink[PATH_MAX]; 66147899Ssam 67147899Ssam if (path[0] == '\0') { 68147899Ssam errno = ENOENT; 69147899Ssam return (NULL); 70147899Ssam } 71147899Ssam 72147899Ssam serrno = errno; 73147899Ssam 74147899Ssam if (resolved == NULL) { 75147899Ssam resolved = malloc(PATH_MAX); 76127668Sbms if (resolved == NULL) 77127668Sbms return (NULL); 78127668Sbms mem_allocated = 1; 79127668Sbms } else 80127668Sbms mem_allocated = 0; 81127668Sbms 82127668Sbms symlinks = 0; 83127668Sbms if (path[0] == '/') { 84127668Sbms resolved[0] = '/'; 85127668Sbms resolved[1] = '\0'; 86127668Sbms if (path[1] == '\0') 87127668Sbms return (resolved); 88147899Ssam resolved_len = 1; 89147899Ssam left_len = strlcpy(left, path + 1, sizeof(left)); 90147899Ssam } else { 91147899Ssam if (getcwd(resolved, PATH_MAX) == NULL) { 92147899Ssam if (mem_allocated) 93147899Ssam free(resolved); 94147899Ssam else 95147899Ssam strlcpy(resolved, ".", PATH_MAX); 96147899Ssam return (NULL); 97147899Ssam } 98147899Ssam resolved_len = strlen(resolved); 99147899Ssam left_len = strlcpy(left, path, sizeof(left)); 100147899Ssam } 101147899Ssam if (left_len >= sizeof(left) || resolved_len >= PATH_MAX) { 102147899Ssam errno = ENAMETOOLONG; 103147899Ssam goto err; 104147899Ssam } 105147899Ssam 106147899Ssam /* 107147899Ssam * Iterate over path components in `left'. 108147899Ssam */ 109147899Ssam while (left_len != 0) { 110147899Ssam /* 111147899Ssam * Extract the next path component and adjust `left' 112147899Ssam * and its length. 113147899Ssam */ 114127668Sbms p = strchr(left, '/'); 115127668Sbms s = p ? p : left + left_len; 116127668Sbms if (s - left >= (ptrdiff_t)sizeof(next_token)) { 117127668Sbms errno = ENAMETOOLONG; 118127668Sbms goto err; 119127668Sbms } 120127668Sbms memcpy(next_token, left, s - left); 121127668Sbms next_token[s - left] = '\0'; 122127668Sbms left_len -= s - left; 123127668Sbms if (p != NULL) 124127668Sbms memmove(left, s + 1, left_len + 1); 125127668Sbms if (resolved[resolved_len - 1] != '/') { 126127668Sbms if (resolved_len + 1 >= PATH_MAX) { 127127668Sbms errno = ENAMETOOLONG; 128127668Sbms goto err; 129127668Sbms } 130127668Sbms resolved[resolved_len++] = '/'; 131127668Sbms resolved[resolved_len] = '\0'; 132127668Sbms } 133147899Ssam if (next_token[0] == '\0') 134127668Sbms continue; 135127668Sbms else if (strcmp(next_token, ".") == 0) 136127668Sbms continue; 137147899Ssam else if (strcmp(next_token, "..") == 0) { 138127668Sbms /* 139127668Sbms * Strip the last path component except when we have 140127668Sbms * single "/" 141127668Sbms */ 142127668Sbms if (resolved_len > 1) { 143127668Sbms resolved[resolved_len - 1] = '\0'; 144127668Sbms q = strrchr(resolved, '/') + 1; 145127668Sbms *q = '\0'; 146127668Sbms resolved_len = q - resolved; 147127668Sbms } 148127668Sbms continue; 149147899Ssam } 150147899Ssam 151147899Ssam /* 152147899Ssam * Append the next path component and lstat() it. If 153147899Ssam * lstat() fails we still can return successfully if 154147899Ssam * there are no more path components left. 155147899Ssam */ 156147899Ssam resolved_len = strlcat(resolved, next_token, PATH_MAX); 157147899Ssam if (resolved_len >= PATH_MAX) { 158147899Ssam errno = ENAMETOOLONG; 159147899Ssam goto err; 160147899Ssam } 161147899Ssam if (lstat(resolved, &sb) != 0) { 162147899Ssam if (errno == ENOENT && p == NULL) { 163147899Ssam errno = serrno; 164147899Ssam return (resolved); 165147899Ssam } 166147899Ssam goto err; 167147899Ssam } 168147899Ssam if (S_ISLNK(sb.st_mode)) { 169127668Sbms if (symlinks++ > SYMLOOP_MAX) { 170127668Sbms errno = ELOOP; 171127668Sbms goto err; 172127668Sbms } 173147899Ssam slen = readlink(resolved, symlink, sizeof(symlink) - 1); 174214478Srpaulo if (slen < 0) 175127668Sbms goto err; 176127668Sbms symlink[slen] = '\0'; 177214478Srpaulo if (symlink[0] == '/') { 178214478Srpaulo resolved[1] = 0; 179214478Srpaulo resolved_len = 1; 180214478Srpaulo } else if (resolved_len > 1) { 181214478Srpaulo /* Strip the last path component. */ 182214478Srpaulo resolved[resolved_len - 1] = '\0'; 183214478Srpaulo q = strrchr(resolved, '/') + 1; 184214478Srpaulo *q = '\0'; 185127668Sbms resolved_len = q - resolved; 186147899Ssam } 187147899Ssam 188127668Sbms /* 189127668Sbms * If there are any path components left, then 190127668Sbms * append them to symlink. The result is placed 191147899Ssam * in `left'. 192127668Sbms */ 193147899Ssam if (p != NULL) { 194127668Sbms if (symlink[slen - 1] != '/') { 195127668Sbms if (slen + 1 >= 196127668Sbms (ptrdiff_t)sizeof(symlink)) { 197127668Sbms errno = ENAMETOOLONG; 198127668Sbms goto err; 199147899Ssam } 200127668Sbms symlink[slen] = '/'; 201127668Sbms symlink[slen + 1] = 0; 202147899Ssam } 203127668Sbms left_len = strlcat(symlink, left, sizeof(symlink)); 204147899Ssam if (left_len >= sizeof(symlink)) { 205127668Sbms errno = ENAMETOOLONG; 206127668Sbms goto err; 207127668Sbms } 208127668Sbms } 209127668Sbms left_len = strlcpy(left, symlink, sizeof(left)); 210127668Sbms } 211127668Sbms } 212127668Sbms 213127668Sbms /* 214127668Sbms * Remove trailing slash except when the resolved pathname 215127668Sbms * is a single "/". 216127668Sbms */ 217127668Sbms if (resolved_len > 1 && resolved[resolved_len - 1] == '/') 218127668Sbms resolved[resolved_len - 1] = '\0'; 219147899Ssam return (resolved); 220147899Ssam 221147899Ssamerr: 222147899Ssam if (mem_allocated) 223147899Ssam free(resolved); 224147899Ssam return (NULL); 225147899Ssam} 226172683Smlaier