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