1 /**
2  * FreeRDP: A Remote Desktop Protocol Implementation
3  * FreeRDP Proxy Server
4  *
5  * Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
6  * Copyright 2019 Idan Freiberg <speidy@gmail.com>
7  *
8  * Licensed under the Apache License, Version 2.0 (the "License");
9  * you may not use this file except in compliance with the License.
10  * You may obtain a copy of the License at
11  *
12  *     http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  */
20 
21 #include <stdio.h>
22 #include <string.h>
23 #include <winpr/crt.h>
24 #include <winpr/collections.h>
25 #include <winpr/cmdline.h>
26 
27 #include "pf_log.h"
28 #include "pf_server.h"
29 #include "pf_config.h"
30 #include "pf_modules.h"
31 
32 #define TAG PROXY_TAG("config")
33 
34 #define CONFIG_PRINT_SECTION(section) WLog_INFO(TAG, "\t%s:", section)
35 #define CONFIG_PRINT_STR(config, key) WLog_INFO(TAG, "\t\t%s: %s", #key, config->key)
36 #define CONFIG_PRINT_BOOL(config, key) \
37 	WLog_INFO(TAG, "\t\t%s: %s", #key, config->key ? "TRUE" : "FALSE")
38 #define CONFIG_PRINT_UINT16(config, key) WLog_INFO(TAG, "\t\t%s: %" PRIu16 "", #key, config->key)
39 #define CONFIG_PRINT_UINT32(config, key) WLog_INFO(TAG, "\t\t%s: %" PRIu32 "", #key, config->key)
40 
pf_config_get_uint16(wIniFile * ini,const char * section,const char * key,UINT16 * result)41 BOOL pf_config_get_uint16(wIniFile* ini, const char* section, const char* key, UINT16* result)
42 {
43 	int val;
44 
45 	val = IniFile_GetKeyValueInt(ini, section, key);
46 	if ((val < 0) || (val > UINT16_MAX))
47 	{
48 		WLog_ERR(TAG, "[%s]: invalid value %d for key '%s.%s'.", __FUNCTION__, val, section, key);
49 		return FALSE;
50 	}
51 
52 	*result = (UINT16)val;
53 	return TRUE;
54 }
55 
pf_config_get_uint32(wIniFile * ini,const char * section,const char * key,UINT32 * result)56 BOOL pf_config_get_uint32(wIniFile* ini, const char* section, const char* key, UINT32* result)
57 {
58 	int val;
59 
60 	val = IniFile_GetKeyValueInt(ini, section, key);
61 	if ((val < 0) || (val > INT32_MAX))
62 	{
63 		WLog_ERR(TAG, "[%s]: invalid value %d for key '%s.%s'.", __FUNCTION__, val, section, key);
64 		return FALSE;
65 	}
66 
67 	*result = (UINT32)val;
68 	return TRUE;
69 }
70 
pf_config_get_bool(wIniFile * ini,const char * section,const char * key)71 BOOL pf_config_get_bool(wIniFile* ini, const char* section, const char* key)
72 {
73 	int num_value;
74 	const char* str_value;
75 
76 	str_value = IniFile_GetKeyValueString(ini, section, key);
77 	if (!str_value)
78 	{
79 		WLog_WARN(TAG, "[%s]: key '%s.%s' not found, value defaults to false.", __FUNCTION__,
80 		          section, key);
81 		return FALSE;
82 	}
83 
84 	if (strcmp(str_value, "TRUE") == 0 || strcmp(str_value, "true") == 0)
85 		return TRUE;
86 
87 	num_value = IniFile_GetKeyValueInt(ini, section, key);
88 
89 	if (num_value == 1)
90 		return TRUE;
91 
92 	return FALSE;
93 }
94 
pf_config_get_str(wIniFile * ini,const char * section,const char * key)95 const char* pf_config_get_str(wIniFile* ini, const char* section, const char* key)
96 {
97 	const char* value;
98 
99 	value = IniFile_GetKeyValueString(ini, section, key);
100 
101 	if (!value)
102 	{
103 		WLog_ERR(TAG, "[%s]: key '%s.%s' not found.", __FUNCTION__, section, key);
104 		return NULL;
105 	}
106 
107 	return value;
108 }
109 
pf_config_load_server(wIniFile * ini,proxyConfig * config)110 static BOOL pf_config_load_server(wIniFile* ini, proxyConfig* config)
111 {
112 	const char* host;
113 
114 	if (!pf_config_get_uint16(ini, "Server", "Port", &config->Port))
115 		return FALSE;
116 
117 	host = pf_config_get_str(ini, "Server", "Host");
118 
119 	if (!host)
120 		return FALSE;
121 
122 	config->Host = _strdup(host);
123 
124 	if (!config->Host)
125 		return FALSE;
126 
127 	return TRUE;
128 }
129 
pf_config_load_target(wIniFile * ini,proxyConfig * config)130 static BOOL pf_config_load_target(wIniFile* ini, proxyConfig* config)
131 {
132 	const char* target_host;
133 
134 	if (!pf_config_get_uint16(ini, "Target", "Port", &config->TargetPort))
135 		return FALSE;
136 
137 	target_host = pf_config_get_str(ini, "Target", "Host");
138 
139 	if (!target_host)
140 		return FALSE;
141 
142 	config->TargetHost = _strdup(target_host);
143 	if (!config->TargetHost)
144 		return FALSE;
145 
146 	config->UseLoadBalanceInfo = pf_config_get_bool(ini, "Target", "UseLoadBalanceInfo");
147 	return TRUE;
148 }
149 
pf_config_load_channels(wIniFile * ini,proxyConfig * config)150 static BOOL pf_config_load_channels(wIniFile* ini, proxyConfig* config)
151 {
152 	config->GFX = pf_config_get_bool(ini, "Channels", "GFX");
153 	config->DisplayControl = pf_config_get_bool(ini, "Channels", "DisplayControl");
154 	config->Clipboard = pf_config_get_bool(ini, "Channels", "Clipboard");
155 	config->AudioOutput = pf_config_get_bool(ini, "Channels", "AudioOutput");
156 	config->RemoteApp = pf_config_get_bool(ini, "Channels", "RemoteApp");
157 	config->Passthrough = CommandLineParseCommaSeparatedValues(
158 	    pf_config_get_str(ini, "Channels", "Passthrough"), &config->PassthroughCount);
159 
160 	{
161 		/* validate channel name length */
162 		size_t i;
163 
164 		for (i = 0; i < config->PassthroughCount; i++)
165 		{
166 			if (strlen(config->Passthrough[i]) > CHANNEL_NAME_LEN)
167 			{
168 				WLog_ERR(TAG, "passthrough channel: %s: name too long!", config->Passthrough[i]);
169 				return FALSE;
170 			}
171 		}
172 	}
173 
174 	return TRUE;
175 }
176 
pf_config_load_input(wIniFile * ini,proxyConfig * config)177 static BOOL pf_config_load_input(wIniFile* ini, proxyConfig* config)
178 {
179 	config->Keyboard = pf_config_get_bool(ini, "Input", "Keyboard");
180 	config->Mouse = pf_config_get_bool(ini, "Input", "Mouse");
181 	return TRUE;
182 }
183 
pf_config_load_security(wIniFile * ini,proxyConfig * config)184 static BOOL pf_config_load_security(wIniFile* ini, proxyConfig* config)
185 {
186 	config->ServerTlsSecurity = pf_config_get_bool(ini, "Security", "ServerTlsSecurity");
187 	config->ServerRdpSecurity = pf_config_get_bool(ini, "Security", "ServerRdpSecurity");
188 
189 	config->ClientTlsSecurity = pf_config_get_bool(ini, "Security", "ClientTlsSecurity");
190 	config->ClientNlaSecurity = pf_config_get_bool(ini, "Security", "ClientNlaSecurity");
191 	config->ClientRdpSecurity = pf_config_get_bool(ini, "Security", "ClientRdpSecurity");
192 	config->ClientAllowFallbackToTls =
193 	    pf_config_get_bool(ini, "Security", "ClientAllowFallbackToTls");
194 	return TRUE;
195 }
196 
pf_config_load_clipboard(wIniFile * ini,proxyConfig * config)197 static BOOL pf_config_load_clipboard(wIniFile* ini, proxyConfig* config)
198 {
199 	config->TextOnly = pf_config_get_bool(ini, "Clipboard", "TextOnly");
200 
201 	if (!pf_config_get_uint32(ini, "Clipboard", "MaxTextLength", &config->MaxTextLength))
202 		return FALSE;
203 
204 	return TRUE;
205 }
206 
pf_config_load_modules(wIniFile * ini,proxyConfig * config)207 static BOOL pf_config_load_modules(wIniFile* ini, proxyConfig* config)
208 {
209 	const char* modules_to_load;
210 	const char* required_modules;
211 
212 	modules_to_load = IniFile_GetKeyValueString(ini, "Plugins", "Modules");
213 	required_modules = IniFile_GetKeyValueString(ini, "Plugins", "Required");
214 
215 	config->Modules = CommandLineParseCommaSeparatedValues(modules_to_load, &config->ModulesCount);
216 
217 	config->RequiredPlugins =
218 	    CommandLineParseCommaSeparatedValues(required_modules, &config->RequiredPluginsCount);
219 	return TRUE;
220 }
221 
pf_config_load_captures(wIniFile * ini,proxyConfig * config)222 static BOOL pf_config_load_captures(wIniFile* ini, proxyConfig* config)
223 {
224 	const char* captures_dir;
225 
226 	config->SessionCapture = pf_config_get_bool(ini, "SessionCapture", "Enabled");
227 	if (!config->SessionCapture)
228 		return TRUE;
229 
230 	captures_dir = pf_config_get_str(ini, "SessionCapture", "CapturesDirectory");
231 
232 	if (!captures_dir)
233 		return FALSE;
234 
235 	config->CapturesDirectory = strdup(captures_dir);
236 	if (!config->CapturesDirectory)
237 		return FALSE;
238 
239 	if (!PathFileExistsA(config->CapturesDirectory))
240 	{
241 		if (!CreateDirectoryA(config->CapturesDirectory, NULL))
242 		{
243 			free(config->CapturesDirectory);
244 			config->CapturesDirectory = NULL;
245 			return FALSE;
246 		}
247 	}
248 
249 	return TRUE;
250 }
251 
pf_server_config_load(const char * path)252 proxyConfig* pf_server_config_load(const char* path)
253 {
254 	proxyConfig* config = NULL;
255 	wIniFile* ini = IniFile_New();
256 
257 	if (!ini)
258 	{
259 		WLog_ERR(TAG, "[%s]: IniFile_New() failed!", __FUNCTION__);
260 		return FALSE;
261 	}
262 
263 	if (IniFile_ReadFile(ini, path) < 0)
264 	{
265 		WLog_ERR(TAG, "[%s] failed to parse ini file: '%s'", __FUNCTION__, path);
266 		goto out;
267 	}
268 
269 	config = calloc(1, sizeof(proxyConfig));
270 
271 	if (!pf_config_load_server(ini, config))
272 		goto out;
273 
274 	if (!pf_config_load_target(ini, config))
275 		goto out;
276 
277 	if (!pf_config_load_channels(ini, config))
278 		goto out;
279 
280 	if (!pf_config_load_input(ini, config))
281 		goto out;
282 
283 	if (!pf_config_load_security(ini, config))
284 		goto out;
285 
286 	if (!pf_config_load_modules(ini, config))
287 		goto out;
288 
289 	if (!pf_config_load_clipboard(ini, config))
290 		goto out;
291 
292 	if (!pf_config_load_captures(ini, config))
293 		goto out;
294 
295 	IniFile_Free(ini);
296 	return config;
297 
298 out:
299 	IniFile_Free(ini);
300 	pf_server_config_free(config);
301 	return NULL;
302 }
303 
pf_server_config_print_list(char ** list,size_t count)304 static void pf_server_config_print_list(char** list, size_t count)
305 {
306 	size_t i;
307 
308 	for (i = 0; i < count; i++)
309 		WLog_INFO(TAG, "\t\t- %s", list[i]);
310 }
311 
pf_server_config_print(proxyConfig * config)312 void pf_server_config_print(proxyConfig* config)
313 {
314 	WLog_INFO(TAG, "Proxy configuration:");
315 
316 	CONFIG_PRINT_SECTION("Server");
317 	CONFIG_PRINT_STR(config, Host);
318 	CONFIG_PRINT_UINT16(config, Port);
319 	CONFIG_PRINT_BOOL(config, SessionCapture);
320 
321 	if (!config->UseLoadBalanceInfo)
322 	{
323 		CONFIG_PRINT_SECTION("Target");
324 		CONFIG_PRINT_STR(config, TargetHost);
325 		CONFIG_PRINT_UINT16(config, TargetPort);
326 	}
327 
328 	CONFIG_PRINT_SECTION("Input");
329 	CONFIG_PRINT_BOOL(config, Keyboard);
330 	CONFIG_PRINT_BOOL(config, Mouse);
331 
332 	CONFIG_PRINT_SECTION("Server Security");
333 	CONFIG_PRINT_BOOL(config, ServerTlsSecurity);
334 	CONFIG_PRINT_BOOL(config, ServerRdpSecurity);
335 
336 	CONFIG_PRINT_SECTION("Client Security");
337 	CONFIG_PRINT_BOOL(config, ClientNlaSecurity);
338 	CONFIG_PRINT_BOOL(config, ClientTlsSecurity);
339 	CONFIG_PRINT_BOOL(config, ClientRdpSecurity);
340 	CONFIG_PRINT_BOOL(config, ClientAllowFallbackToTls);
341 
342 	CONFIG_PRINT_SECTION("Channels");
343 	CONFIG_PRINT_BOOL(config, GFX);
344 	CONFIG_PRINT_BOOL(config, DisplayControl);
345 	CONFIG_PRINT_BOOL(config, Clipboard);
346 	CONFIG_PRINT_BOOL(config, AudioOutput);
347 	CONFIG_PRINT_BOOL(config, RemoteApp);
348 
349 	if (config->PassthroughCount)
350 	{
351 		WLog_INFO(TAG, "\tStatic Channels Proxy:");
352 		pf_server_config_print_list(config->Passthrough, config->PassthroughCount);
353 	}
354 
355 	CONFIG_PRINT_SECTION("Clipboard");
356 	CONFIG_PRINT_BOOL(config, TextOnly);
357 	if (config->MaxTextLength > 0)
358 		CONFIG_PRINT_UINT32(config, MaxTextLength);
359 
360 	CONFIG_PRINT_SECTION("SessionCapture");
361 	CONFIG_PRINT_BOOL(config, SessionCapture);
362 	CONFIG_PRINT_STR(config, CapturesDirectory);
363 }
364 
pf_server_config_free(proxyConfig * config)365 void pf_server_config_free(proxyConfig* config)
366 {
367 	if (config == NULL)
368 		return;
369 
370 	free(config->Passthrough);
371 	free(config->CapturesDirectory);
372 	free(config->RequiredPlugins);
373 	free(config->Modules);
374 	free(config->TargetHost);
375 	free(config->Host);
376 	free(config);
377 }
378