1 /*
2  *  Copyright (C) 2013-2022 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
3  *  Copyright (C) 2009-2013 Sourcefire, Inc.
4  *
5  *  Authors: aCaB <acab@clamav.net>
6  *
7  *  This program is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License version 2 as
9  *  published by the Free Software Foundation.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program; if not, write to the Free Software
18  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19  *  MA 02110-1301, USA.
20  */
21 
22 #include <errno.h>
23 #include <sys/types.h>
24 #include <sys/stat.h>
25 #include <fcntl.h>
26 #include <io.h>
27 
28 #include <Windows.h>
29 #include <WinNls.h>
30 
31 #include "clamav-types.h"
32 
33 #include "dirent.h"
34 #include "w32_stat.h"
35 
is_abspath(const char * path)36 static int is_abspath(const char *path)
37 {
38     int len = strlen(path);
39     return (len > 2 && path[0] == '\\' && path[1] == '\\') || (len >= 2 && ((*path >= 'a' && *path <= 'z') || (*path >= 'A' && *path <= 'Z')) && path[1] == ':');
40 }
41 
uncpath(const char * path)42 wchar_t *uncpath(const char *path)
43 {
44     DWORD len = 0;
45     char utf8[PATH_MAX + 1];
46     wchar_t *stripme, *strip_from, *dest = malloc((PATH_MAX + 1) * sizeof(wchar_t));
47 
48     if (!dest)
49         return NULL;
50 
51     if (strncmp(path, "\\\\", 2)) {
52         /* NOT already UNC */
53         memcpy(dest, L"\\\\?\\", 8);
54 
55         if (!is_abspath(path)) {
56             /* Relative path */
57             len = GetCurrentDirectoryW(PATH_MAX - 5, &dest[4]);
58             if (!len || len > PATH_MAX - 5) {
59                 free(dest);
60                 errno = (len || (GetLastError() == ERROR_INSUFFICIENT_BUFFER)) ? ENAMETOOLONG : ENOENT;
61                 return NULL;
62             }
63             if (*path == '\\')
64                 len = 6; /* Current drive root */
65             else {
66                 len += 4; /* A 'really' relative path */
67                 dest[len] = L'\\';
68                 len++;
69             }
70         } else {
71             /* C:\ and friends */
72             len = 4;
73         }
74     } else {
75         /* UNC already */
76         len = 0;
77     }
78 
79     /* TODO: DROP THE ACP STUFF ONCE WE'RE ALL CONVERTED TO UTF-8 */
80     if (MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, path, -1, &dest[len], PATH_MAX - len) &&
81         WideCharToMultiByte(CP_UTF8, 0, &dest[len], -1, utf8, PATH_MAX, NULL, NULL) &&
82         !strcmp(path, utf8)) {
83     } else if (!(len = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, path, -1, &dest[len], PATH_MAX - len)) || len > PATH_MAX - len) {
84         free(dest);
85         errno = (len || (GetLastError() == ERROR_INSUFFICIENT_BUFFER)) ? ENAMETOOLONG : ENOENT;
86         return NULL;
87     }
88 
89     len        = wcslen(dest);
90     strip_from = &dest[3];
91     /* append a backslash to naked drives and get rid of . and .. */
92     if (!wcsncmp(dest, L"\\\\?\\", 4) && (dest[5] == L':') && ((dest[4] >= L'A' && dest[4] <= L'Z') || (dest[4] >= L'a' && dest[4] <= L'z'))) {
93         if (len == 6) {
94             dest[6] = L'\\';
95             dest[7] = L'\0';
96         }
97         strip_from = &dest[6];
98     }
99     while ((stripme = wcsstr(strip_from, L"\\."))) {
100         wchar_t *copy_from, *copy_to;
101         if (!stripme[2] || stripme[2] == L'\\') {
102             copy_from = &stripme[2];
103             copy_to   = stripme;
104         } else if (stripme[2] == L'.' && (!stripme[3] || stripme[3] == L'\\')) {
105             *stripme  = L'\0';
106             copy_from = &stripme[3];
107             copy_to   = wcsrchr(strip_from, L'\\');
108             if (!copy_to)
109                 copy_to = stripme;
110         } else {
111             strip_from = &stripme[1];
112             continue;
113         }
114         while (1) {
115             *copy_to = *copy_from;
116             if (!*copy_from) break;
117             copy_to++;
118             copy_from++;
119         }
120     }
121 
122     /* strip double slashes */
123     if ((stripme = wcsstr(&dest[4], L"\\\\"))) {
124         strip_from = stripme;
125         while (1) {
126             wchar_t c = *strip_from;
127             strip_from++;
128             if (c == L'\\' && *strip_from == L'\\')
129                 continue;
130             *stripme = c;
131             stripme++;
132             if (!c)
133                 break;
134         }
135     }
136     if (wcslen(dest) == 6 && !wcsncmp(dest, L"\\\\?\\", 4) && (dest[5] == L':') && ((dest[4] >= L'A' && dest[4] <= L'Z') || (dest[4] >= L'a' && dest[4] <= L'z'))) {
137         dest[6] = L'\\';
138         dest[7] = L'\0';
139     }
140     return dest;
141 }
142 
safe_open(const char * path,int flags,...)143 int safe_open(const char *path, int flags, ...)
144 {
145     wchar_t *wpath = uncpath(path);
146     int ret;
147 
148     if (!wpath)
149         return -1;
150 
151     if (flags & O_CREAT) {
152         int mode;
153         va_list ap;
154         va_start(ap, flags);
155         mode = va_arg(ap, int);
156         va_end(ap);
157         ret = _wopen(wpath, flags, mode);
158     } else
159         ret = _wopen(wpath, flags);
160     free(wpath);
161     return ret;
162 }
163 
FileTimeToUnixTime(FILETIME t)164 static time_t FileTimeToUnixTime(FILETIME t)
165 {
166     LONGLONG ll = ((LONGLONG)t.dwHighDateTime << 32) | t.dwLowDateTime;
167     ll -= 116444736000000000;
168     return (time_t)(ll / 10000000);
169 }
170 
w32_stat(const char * path,struct stat * buf)171 int w32_stat(const char *path, struct stat *buf)
172 {
173     int len;
174     wchar_t *wpath = uncpath(path);
175     WIN32_FILE_ATTRIBUTE_DATA attrs;
176 
177     if (!wpath) {
178         errno = ENOMEM;
179         return -1;
180     }
181 
182     len = GetFileAttributesExW(wpath, GetFileExInfoStandard, &attrs);
183     free(wpath);
184     if (!len) {
185         errno = ENOENT;
186         return -1;
187     }
188     buf->st_dev   = 1;
189     buf->st_rdev  = 1;
190     buf->st_uid   = 0;
191     buf->st_gid   = 0;
192     buf->st_ino   = 1;
193     buf->st_atime = FileTimeToUnixTime(attrs.ftLastAccessTime);
194     buf->st_ctime = FileTimeToUnixTime(attrs.ftCreationTime);
195     buf->st_mtime = FileTimeToUnixTime(attrs.ftLastWriteTime);
196     buf->st_mode  = (attrs.dwFileAttributes & FILE_ATTRIBUTE_READONLY) ? S_IRUSR : S_IRWXU;
197     buf->st_mode |= (attrs.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? _S_IFDIR : _S_IFREG;
198     buf->st_nlink = 1;
199     buf->st_size  = ((uint64_t)attrs.nFileSizeHigh << (sizeof(attrs.nFileSizeLow) * 8)) | attrs.nFileSizeLow;
200     return 0;
201 }
202 
w32_access(const char * pathname,int mode)203 int w32_access(const char *pathname, int mode)
204 {
205     int ret;
206     HANDLE hFile          = INVALID_HANDLE_VALUE;
207     DWORD dwDesiredAccess = GENERIC_READ;
208 
209     if (W_OK & mode) {
210         dwDesiredAccess = dwDesiredAccess | GENERIC_WRITE;
211     }
212 
213     hFile = CreateFileA(pathname, // file to open
214                         dwDesiredAccess,
215                         FILE_SHARE_READ,            // share for reading
216                         NULL,                       // default security
217                         OPEN_EXISTING,              // existing file only
218                         FILE_FLAG_BACKUP_SEMANTICS, // may be a directory
219                         NULL);
220     if (hFile == INVALID_HANDLE_VALUE) {
221         if (GetLastError() == ERROR_ACCESS_DENIED) {
222             _set_errno(EACCES);
223         } else {
224             _set_errno(ENOENT);
225         }
226         ret = -1;
227     } else {
228         CloseHandle(hFile);
229         ret = 0;
230     }
231     return ret;
232 }
233 
234 #undef rename
w32_rename(const char * oldname,const char * newname)235 int w32_rename(const char *oldname, const char *newname)
236 {
237     DWORD dwAttrs = GetFileAttributes(newname);
238     SetFileAttributes(newname, dwAttrs & ~(FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN));
239     if (!DeleteFileA(newname) && (GetLastError() != ERROR_FILE_NOT_FOUND)) {
240         return -1;
241     }
242     return rename(oldname, newname);
243 }
244