1 /* vim: set sw=4 ts=4 noexpandtab : */
2 /*
3 * Copyright (C) 2015-2019 Abel Cheung.
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * 3. Neither the name of the project nor the names of its contributors
15 * may be used to endorse or promote products derived from this software
16 * without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28 * SUCH DAMAGE.
29 */
30
31 #include "config.h"
32 #include "utils-win.h"
33
34 #include <lmcons.h>
35 #include <windows.h>
36 #include <aclapi.h>
37 #include <authz.h>
38
39 #include <glib.h>
40 #include <glib/gi18n.h>
41
42
43 HANDLE wincon_fh = NULL;
44
45
46 /* GUI message box */
47 void
gui_message(const char * message)48 gui_message (const char *message)
49 {
50 gunichar2 *title, *body;
51 GError *error = NULL;
52
53 title = g_utf8_to_utf16 (_("This is a command line application"),
54 -1, NULL, NULL, &error);
55 if (error) {
56 g_clear_error (&error);
57 title = g_utf8_to_utf16 ("This is a command line application",
58 -1, NULL, NULL, NULL);
59 }
60
61 body = g_utf8_to_utf16 (message, -1, NULL, NULL, &error);
62 if (error) {
63 g_clear_error (&error);
64 body = g_utf8_to_utf16 ("(Original message failed to be displayed in UTF-16)",
65 -1, NULL, NULL, NULL);
66 }
67
68 /* Takes advantage of the fact that LPCWSTR (wchar_t) is actually 16bit on Windows */
69 MessageBoxW (NULL, (LPCWSTR) body, (LPCWSTR) title,
70 MB_OK | MB_ICONINFORMATION | MB_TOPMOST);
71 g_free (title);
72 g_free (body);
73 }
74
75 /*!
76 * A copy of latter part of g_win32_getlocale()
77 */
78 #ifndef SUBLANG_SERBIAN_LATIN_BA
79 #define SUBLANG_SERBIAN_LATIN_BA 0x06
80 #endif
81
82 static const char *
get_win32_locale_script(int primary,int sub)83 get_win32_locale_script (int primary,
84 int sub)
85 {
86 switch (primary)
87 {
88 case LANG_AZERI:
89 switch (sub)
90 {
91 case SUBLANG_AZERI_LATIN: return "@Latn";
92 case SUBLANG_AZERI_CYRILLIC: return "@Cyrl";
93 }
94 break;
95
96 case LANG_SERBIAN: /* LANG_CROATIAN == LANG_SERBIAN */
97 switch (sub)
98 {
99 case SUBLANG_SERBIAN_LATIN:
100 case SUBLANG_SERBIAN_LATIN_BA: /* Serbian (Latin) - Bosnia and Herzegovina */
101 return "@Latn";
102 }
103 break;
104 case LANG_UZBEK:
105 switch (sub)
106 {
107 case SUBLANG_UZBEK_LATIN: return "@Latn";
108 case SUBLANG_UZBEK_CYRILLIC: return "@Cyrl";
109 }
110 break;
111 }
112 return NULL;
113 }
114
115 /*!
116 * We can't use [`g_win32_getlocale()`][1] directly.
117 *
118 * There are 3 possible source for language UI settings:
119 * - [`GetThreadLocale()`][2] (used by g_win32_getlocale)
120 * - User default language
121 * - System default language
122 *
123 * For GUI applications, thread locale is good enough; this is the
124 * default for gettext and glib on Windows. But rifiuti2 is a CLI
125 * program, where the caller is a console. In such case thread locale
126 * is solely determined by console code page, not always equal to user
127 * preferred language.
128 *
129 * For example, multiple West European Windows use console codepage 850,
130 * which GetLocaleInfo() returns en_US, not any West European languages.
131 *
132 * Here we attempt to pick up user default first, followed by thread
133 * locale; and do the dirty work in a manner almost identical to
134 * g_win32_getlocale().
135 *
136 * [1]: https://developer.gnome.org/glib/stable/glib-Windows-Compatibility-Functions.html#g-win32-getlocale
137 * [2]: https://docs.microsoft.com/en-us/windows/desktop/api/winnls/nf-winnls-getthreadlocale
138 */
139 char *
get_win32_locale(void)140 get_win32_locale (void)
141 {
142 LCID lcid;
143 LANGID langid;
144 char *ev;
145 char iso639[10];
146 char iso3166[10];
147 const char *script;
148
149 /* Allow user overriding locale env */
150 if (((ev = getenv ("LC_ALL")) != NULL && ev[0] != '\0') ||
151 ((ev = getenv ("LC_MESSAGES")) != NULL && ev[0] != '\0') ||
152 ((ev = getenv ("LANG")) != NULL && ev[0] != '\0'))
153 return g_strdup (ev);
154
155 lcid = LOCALE_USER_DEFAULT;
156 if (!GetLocaleInfo (lcid, LOCALE_SISO639LANGNAME , iso639 , sizeof (iso639)) ||
157 !GetLocaleInfo (lcid, LOCALE_SISO3166CTRYNAME, iso3166, sizeof (iso3166)))
158 {
159 lcid = GetThreadLocale();
160 if (!GetLocaleInfo (lcid, LOCALE_SISO639LANGNAME , iso639 , sizeof (iso639)) ||
161 !GetLocaleInfo (lcid, LOCALE_SISO3166CTRYNAME, iso3166, sizeof (iso3166)))
162 return g_strdup ("C");
163 }
164
165 /* Strip off the sorting rules, keep only the language part. */
166 langid = LANGIDFROMLCID (lcid);
167
168 /* Get script based on language and territory */
169 script = get_win32_locale_script (PRIMARYLANGID (langid), SUBLANGID (langid));
170
171 return g_strconcat (iso639, "_", iso3166, script, NULL);
172 }
173
174 /*!
175 * `strftime()` on Windows can show garbage timezone name, because its
176 * encoding does not match console codepage. For example, strftime %Z result
177 * is in CP936 encoding for zh-HK, while console codepage is CP950.
178 *
179 * OTOH, `wcsftime() %Z` would not return anything if console codepage is
180 * set to any non-default codepage for current system.
181 *
182 * `GetTimeZoneInformation()` returns sensible result regardless of
183 * console codepage setting.
184 */
185 char *
get_win_timezone_name(void)186 get_win_timezone_name (void)
187 {
188 TIME_ZONE_INFORMATION tzinfo;
189 wchar_t *name;
190 DWORD id;
191 char *ret;
192 GError *err = NULL;
193
194 id = GetTimeZoneInformation (&tzinfo);
195
196 switch (id)
197 {
198 case TIME_ZONE_ID_UNKNOWN:
199 case TIME_ZONE_ID_STANDARD:
200 name = tzinfo.StandardName;
201 break;
202 case TIME_ZONE_ID_DAYLIGHT:
203 name = tzinfo.DaylightName;
204 break;
205 default:
206 ret = g_win32_error_message (GetLastError ());
207 g_critical ("%s", ret);
208 g_free (ret);
209 return g_strdup (_("(Failed to retrieve timezone name)"));
210 break;
211 }
212
213 ret = g_utf16_to_utf8 ( (const gunichar2 *) name, -1, NULL, NULL, &err);
214 if (err == NULL)
215 return ret;
216
217 g_warning ("%s", err->message);
218 g_error_free (err);
219 return g_strdup (_("(Failed to retrieve timezone name)"));
220 }
221
222
223 /*!
224 * Retrieve current user name and convert it to SID
225 *
226 * Following functions originates from [example of `GetEffectiveRightsFromAcl()`][1],
227 * which is not about the function itself but a _replacement_ of it (shrug).
228 *
229 * [1]: https://msdn.microsoft.com/en-us/library/windows/desktop/aa446637(v=vs.85).aspx
230 */
231 static PSID
get_user_sid(void)232 get_user_sid (void)
233 {
234 gboolean status;
235 char username[UNLEN + 1], *errmsg;
236 DWORD err = 0, bufsize = UNLEN + 1, sidsize = 0, domainsize = 0;
237 PSID sid;
238 LPTSTR domainname;
239 SID_NAME_USE sidtype;
240
241 if ( !GetUserName (username, &bufsize) )
242 {
243 errmsg = g_win32_error_message (GetLastError());
244 g_critical (_("Failed to get current user name: %s"), errmsg);
245 goto getsid_fail;
246 }
247
248 status = LookupAccountName (NULL, username, NULL, &sidsize,
249 NULL, &domainsize, &sidtype);
250 if ( !status )
251 err = GetLastError();
252 g_debug ("1st LookupAccountName(): status = %d", (int) status);
253
254 if ( err != ERROR_INSUFFICIENT_BUFFER )
255 {
256 errmsg = g_win32_error_message (err);
257 g_critical (_("LookupAccountName() failed: %s"), errmsg);
258 goto getsid_fail;
259 }
260
261 sid = (PSID) g_malloc (sidsize);
262 domainname = (LPTSTR) g_malloc (domainsize);
263
264 status = LookupAccountName (NULL, username, sid, &sidsize,
265 domainname, &domainsize, &sidtype);
266 err = status ? 0 : GetLastError();
267 g_debug ("2nd LookupAccountName(): status = %d", (int) status);
268 g_free (domainname); /* unused */
269
270 if ( status != 0 )
271 return sid; /* success */
272
273 errmsg = g_win32_error_message (err);
274 g_critical (_("LookupAccountName() failed: %s"), errmsg);
275 g_free (sid);
276
277 getsid_fail:
278 g_free (errmsg);
279 return NULL;
280 }
281
282 /*!
283 * Fetch ACL access mask using Authz API
284 */
285 gboolean
can_list_win32_folder(const char * path)286 can_list_win32_folder (const char *path)
287 {
288 char *errmsg = NULL;
289 gunichar2 *wpath;
290 gboolean ret = FALSE;
291 PSID sid;
292 DWORD dw, dw2;
293 PSECURITY_DESCRIPTOR sec_desc;
294 ACCESS_MASK mask;
295 AUTHZ_RESOURCE_MANAGER_HANDLE authz_manager;
296 AUTHZ_CLIENT_CONTEXT_HANDLE authz_ctxt = NULL;
297 AUTHZ_ACCESS_REQUEST authz_req = { MAXIMUM_ALLOWED, NULL, NULL, 0, NULL };
298 AUTHZ_ACCESS_REPLY authz_reply;
299
300 if ( NULL == ( sid = get_user_sid() ) )
301 return FALSE;
302
303 wpath = g_utf8_to_utf16 (path, -1, NULL, NULL, NULL);
304 if (wpath == NULL)
305 return FALSE;
306
307 if ( !AuthzInitializeResourceManager (AUTHZ_RM_FLAG_NO_AUDIT,
308 NULL, NULL, NULL, NULL, &authz_manager) )
309 {
310 errmsg = g_win32_error_message (GetLastError());
311 g_printerr (_("AuthzInitializeResourceManager() failed: %s"), errmsg);
312 g_printerr ("\n");
313 goto traverse_fail;
314 }
315
316 dw = GetNamedSecurityInfoW ((wchar_t *)wpath, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION |
317 OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION,
318 NULL, NULL, NULL, NULL, &sec_desc);
319 if ( dw != ERROR_SUCCESS )
320 {
321 errmsg = g_win32_error_message (dw);
322 g_printerr (_("Failed to retrieve Discretionary ACL info for '%s': %s"), path, errmsg);
323 g_printerr ("\n");
324 goto traverse_getacl_fail;
325 }
326
327 if ( !AuthzInitializeContextFromSid (0, sid, authz_manager,
328 NULL, (LUID) {0} /* unused */, NULL, &authz_ctxt) )
329 {
330 errmsg = g_win32_error_message (GetLastError());
331 g_printerr (_("AuthzInitializeContextFromSid() failed: %s"), errmsg);
332 g_printerr ("\n");
333 goto traverse_getacl_fail;
334 }
335
336 authz_reply = (AUTHZ_ACCESS_REPLY) { 1, &mask, &dw2, &dw }; /* last 2 param unused */
337 if ( !AuthzAccessCheck (0, authz_ctxt, &authz_req, NULL, sec_desc,
338 NULL, 0, &authz_reply, NULL ) )
339 {
340 errmsg = g_win32_error_message (GetLastError());
341 g_printerr (_("AuthzAccessCheck() failed: %s"), errmsg);
342 g_printerr ("\n");
343 }
344 else
345 {
346 /*
347 * We only need permission to list directory; even directory traversal is
348 * not needed, because we are going to access the files directly later.
349 * Unlike Unix, no read permission on parent folder is needed to list
350 * files within.
351 */
352 if ( (mask & FILE_LIST_DIRECTORY) == FILE_LIST_DIRECTORY &&
353 (mask & FILE_READ_EA) == FILE_READ_EA )
354 ret = TRUE;
355 else {
356 g_printerr (_("Error listing directory: Insufficient permission."));
357 g_printerr ("\n");
358 }
359
360 /* use glib type to avoid including more header */
361 g_debug ("Access Mask hex for '%s': 0x%X", path, (guint32) mask);
362 }
363
364 AuthzFreeContext (authz_ctxt);
365
366 traverse_getacl_fail:
367 LocalFree (sec_desc);
368 AuthzFreeResourceManager (authz_manager);
369
370 traverse_fail:
371 g_free (sid);
372 g_free (errmsg);
373 g_free (wpath);
374 return ret;
375 }
376
377 /*!
378 * Initialize console handle under Windows
379 *
380 * Used only when output is Windows native console. For all other cases
381 * unix-style file stream is used.
382 */
383 gboolean
init_wincon_handle(void)384 init_wincon_handle (void)
385 {
386 HANDLE h = GetStdHandle (STD_OUTPUT_HANDLE);
387
388 /*
389 * FILE_TYPE_CHAR only happens when output is a native Windows cmd
390 * console. For Cygwin and Msys shell environments (and output redirection),
391 * GetFileType() would return FILE_TYPE_PIPE. In those cases printf
392 * family outputs UTF-8 data properly. Only Windows console needs to be
393 * dealt with using wide char API.
394 */
395 if (GetFileType (h) == FILE_TYPE_CHAR) {
396 wincon_fh = h;
397 return TRUE;
398 }
399 return FALSE;
400 }
401
402 void
close_wincon_handle(void)403 close_wincon_handle (void)
404 {
405 if (wincon_fh != NULL)
406 CloseHandle (wincon_fh);
407 return;
408 }
409
410 gboolean
puts_wincon(const wchar_t * wstr)411 puts_wincon (const wchar_t *wstr)
412 {
413 g_return_val_if_fail (wstr != NULL, FALSE);
414 g_return_val_if_fail (wincon_fh != NULL, FALSE);
415
416 if (WriteConsoleW (wincon_fh, wstr, wcslen (wstr), NULL, NULL))
417 return TRUE;
418 else
419 return FALSE;
420 }
421