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