OpenVPN
validate.c
Go to the documentation of this file.
1 /*
2  * OpenVPN -- An application to securely tunnel IP networks
3  * over a single TCP/UDP port, with support for SSL/TLS-based
4  * session authentication and key exchange,
5  * packet encryption, packet authentication, and
6  * packet compression.
7  *
8  * Copyright (C) 2016 Selva Nair <selva.nair@gmail.com>
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License version 2
12  * as published by the Free Software Foundation.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License along
20  * with this program; if not, write to the Free Software Foundation, Inc.,
21  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22  */
23 
24 #include "validate.h"
25 
26 #include <lmaccess.h>
27 #include <shlwapi.h>
28 #include <lm.h>
29 
30 static const WCHAR *white_list[] =
31 {
32  L"auth-retry",
33  L"config",
34  L"log",
35  L"log-append",
36  L"management",
37  L"management-forget-disconnect",
38  L"management-hold",
39  L"management-query-passwords",
40  L"management-query-proxy",
41  L"management-signal",
42  L"management-up-down",
43  L"mute",
44  L"setenv",
45  L"service",
46  L"verb",
47  L"pull-filter",
48  L"script-security",
49 
50  NULL /* last value */
51 };
52 
53 static BOOL IsUserInGroup(PSID sid, const PTOKEN_GROUPS groups, const WCHAR *group_name);
54 
55 static PTOKEN_GROUPS GetTokenGroups(const HANDLE token);
56 
57 /*
58  * Check workdir\fname is inside config_dir
59  * The logic here is simple: we may reject some valid paths if ..\ is in any of the strings
60  */
61 static BOOL
62 CheckConfigPath(const WCHAR *workdir, const WCHAR *fname, const settings_t *s)
63 {
64  WCHAR tmp[MAX_PATH];
65  const WCHAR *config_file = NULL;
66  const WCHAR *config_dir = NULL;
67 #ifndef UNICODE
68  WCHAR widepath[MAX_PATH];
69 #endif
70 
71  /* convert fname to full path */
72  if (PathIsRelativeW(fname) )
73  {
74  openvpn_swprintf(tmp, _countof(tmp), L"%s\\%s", workdir, fname);
75  config_file = tmp;
76  }
77  else
78  {
79  config_file = fname;
80  }
81 
82 #ifdef UNICODE
83  config_dir = s->config_dir;
84 #else
85  if (MultiByteToWideChar(CP_UTF8, 0, s->config_dir, -1, widepath, MAX_PATH) == 0)
86  {
87  MsgToEventLog(M_SYSERR, TEXT("Failed to convert config_dir name to WideChar"));
88  return FALSE;
89  }
90  config_dir = widepath;
91 #endif
92 
93  if (wcsncmp(config_dir, config_file, wcslen(config_dir)) == 0
94  && wcsstr(config_file + wcslen(config_dir), L"..") == NULL)
95  {
96  return TRUE;
97  }
98 
99  return FALSE;
100 }
101 
102 
103 /*
104  * A simple linear search meant for a small wchar_t *array.
105  * Returns index to the item if found, -1 otherwise.
106  */
107 static int
108 OptionLookup(const WCHAR *name, const WCHAR *white_list[])
109 {
110  int i;
111 
112  for (i = 0; white_list[i]; i++)
113  {
114  if (wcscmp(white_list[i], name) == 0)
115  {
116  return i;
117  }
118  }
119 
120  return -1;
121 }
122 
123 /*
124  * The Administrators group may be localized or renamed by admins.
125  * Get the local name of the group using the SID.
126  */
127 static BOOL
128 GetBuiltinAdminGroupName(WCHAR *name, DWORD nlen)
129 {
130  BOOL b = FALSE;
131  PSID admin_sid = NULL;
132  DWORD sid_size = SECURITY_MAX_SID_SIZE;
133  SID_NAME_USE snu;
134 
135  WCHAR domain[MAX_NAME];
136  DWORD dlen = _countof(domain);
137 
138  admin_sid = malloc(sid_size);
139  if (!admin_sid)
140  {
141  return FALSE;
142  }
143 
144  b = CreateWellKnownSid(WinBuiltinAdministratorsSid, NULL, admin_sid, &sid_size);
145  if (b)
146  {
147  b = LookupAccountSidW(NULL, admin_sid, name, &nlen, domain, &dlen, &snu);
148  }
149 
150  free(admin_sid);
151 
152  return b;
153 }
154 
155 /*
156  * Check whether user is a member of Administrators group or
157  * the group specified in ovpn_admin_group
158  */
159 BOOL
160 IsAuthorizedUser(PSID sid, const HANDLE token, const WCHAR *ovpn_admin_group)
161 {
162  const WCHAR *admin_group[2];
163  WCHAR username[MAX_NAME];
164  WCHAR domain[MAX_NAME];
165  WCHAR sysadmin_group[MAX_NAME];
166  DWORD len = MAX_NAME;
167  BOOL ret = FALSE;
168  SID_NAME_USE sid_type;
169 
170  /* Get username */
171  if (!LookupAccountSidW(NULL, sid, username, &len, domain, &len, &sid_type))
172  {
173  MsgToEventLog(M_SYSERR, TEXT("LookupAccountSid"));
174  /* not fatal as this is now used only for logging */
175  username[0] = '\0';
176  domain[0] = '\0';
177  }
178 
179  if (GetBuiltinAdminGroupName(sysadmin_group, _countof(sysadmin_group)))
180  {
181  admin_group[0] = sysadmin_group;
182  }
183  else
184  {
185  MsgToEventLog(M_SYSERR, TEXT("Failed to get the name of Administrators group. Using the default."));
186  /* use the default value */
187  admin_group[0] = SYSTEM_ADMIN_GROUP;
188  }
189  admin_group[1] = ovpn_admin_group;
190 
191  PTOKEN_GROUPS token_groups = GetTokenGroups(token);
192  for (int i = 0; i < 2; ++i)
193  {
194  ret = IsUserInGroup(sid, token_groups, admin_group[i]);
195  if (ret)
196  {
197  MsgToEventLog(M_INFO, TEXT("Authorizing user '%s@%s' by virtue of membership in group '%s'"),
198  username, domain, admin_group[i]);
199  goto out;
200  }
201  }
202 
203 out:
204  free(token_groups);
205  return ret;
206 }
207 
213 static PTOKEN_GROUPS
214 GetTokenGroups(const HANDLE token)
215 {
216  PTOKEN_GROUPS groups = NULL;
217  DWORD buf_size = 0;
218 
219  if (!GetTokenInformation(token, TokenGroups, groups, buf_size, &buf_size)
220  && GetLastError() == ERROR_INSUFFICIENT_BUFFER)
221  {
222  groups = malloc(buf_size);
223  }
224  if (!groups)
225  {
226  MsgToEventLog(M_SYSERR, L"GetTokenGroups");
227  }
228  else if (!GetTokenInformation(token, TokenGroups, groups, buf_size, &buf_size))
229  {
230  MsgToEventLog(M_SYSERR, L"GetTokenInformation");
231  free(groups);
232  }
233  return groups;
234 }
235 
236 /*
237  * Find SID from name
238  *
239  * On input sid buffer should have space for at least sid_size bytes.
240  * Returns true on success, false on failure.
241  * Suggest: in caller allocate sid to hold SECURITY_MAX_SID_SIZE bytes
242  */
243 static BOOL
244 LookupSID(const WCHAR *name, PSID sid, DWORD sid_size)
245 {
246  SID_NAME_USE su;
247  WCHAR domain[MAX_NAME];
248  DWORD dlen = _countof(domain);
249 
250  if (!LookupAccountName(NULL, name, sid, &sid_size, domain, &dlen, &su))
251  {
252  return FALSE; /* not fatal as the group may not exist */
253  }
254  return TRUE;
255 }
256 
269 static BOOL
270 IsUserInGroup(PSID sid, const PTOKEN_GROUPS token_groups, const WCHAR *group_name)
271 {
272  BOOL ret = FALSE;
273  DWORD_PTR resume = 0;
274  DWORD err;
275  BYTE grp_sid[SECURITY_MAX_SID_SIZE];
276  int nloop = 0; /* a counter used to not get stuck in the do .. while() */
277 
278  /* first check in the token groups */
279  if (token_groups && LookupSID(group_name, (PSID) grp_sid, _countof(grp_sid)))
280  {
281  for (DWORD i = 0; i < token_groups->GroupCount; ++i)
282  {
283  if (EqualSid((PSID) grp_sid, token_groups->Groups[i].Sid))
284  {
285  return TRUE;
286  }
287  }
288  }
289 
290  /* check user's SID is a member of the group */
291  if (!sid)
292  {
293  return FALSE;
294  }
295  do
296  {
297  DWORD nread, nmax;
298  LOCALGROUP_MEMBERS_INFO_0 *members = NULL;
299  err = NetLocalGroupGetMembers(NULL, group_name, 0, (LPBYTE *) &members,
300  MAX_PREFERRED_LENGTH, &nread, &nmax, &resume);
301  if ((err != NERR_Success && err != ERROR_MORE_DATA))
302  {
303  break;
304  }
305  /* If a match is already found, ret == TRUE and the loop is skipped */
306  for (DWORD i = 0; i < nread && !ret; ++i)
307  {
308  ret = EqualSid(members[i].lgrmi0_sid, sid);
309  }
310  NetApiBufferFree(members);
311  /* MSDN says the lookup should always iterate until err != ERROR_MORE_DATA */
312  } while (err == ERROR_MORE_DATA && nloop++ < 100);
313 
314  if (err != NERR_Success && err != NERR_GroupNotFound)
315  {
316  SetLastError(err);
317  MsgToEventLog(M_SYSERR, TEXT("In NetLocalGroupGetMembers for group '%s'"), group_name);
318  }
319 
320  return ret;
321 }
322 
323 /*
324  * Check whether option argv[0] is white-listed. If argv[0] == "--config",
325  * also check that argv[1], if present, passes CheckConfigPath().
326  * The caller should set argc to the number of valid elements in argv[] array.
327  */
328 BOOL
329 CheckOption(const WCHAR *workdir, int argc, WCHAR *argv[], const settings_t *s)
330 {
331  /* Do not modify argv or *argv -- ideally it should be const WCHAR *const *, but alas...*/
332 
333  if (wcscmp(argv[0], L"--config") == 0
334  && argc > 1
335  && !CheckConfigPath(workdir, argv[1], s)
336  )
337  {
338  return FALSE;
339  }
340 
341  /* option name starts at 2 characters from argv[i] */
342  if (OptionLookup(argv[0] + 2, white_list) == -1) /* not found */
343  {
344  return FALSE;
345  }
346 
347  return TRUE;
348 }
DWORD MsgToEventLog(DWORD flags, LPCTSTR format,...)
Definition: common.c:256
#define M_INFO
Definition: errlevel.h:55
TCHAR config_dir[MAX_PATH]
Definition: service.h:67
BOOL CheckOption(const WCHAR *workdir, int argc, WCHAR *argv[], const settings_t *s)
Definition: validate.c:329
static PTOKEN_GROUPS GetTokenGroups(const HANDLE token)
Get a list of groups in token.
Definition: validate.c:214
#define SYSTEM_ADMIN_GROUP
Definition: validate.h:31
static BOOL IsUserInGroup(PSID sid, const PTOKEN_GROUPS groups, const WCHAR *group_name)
User is in group if the token groups contain the SID of the group of if the user is a direct member o...
Definition: validate.c:270
static const WCHAR * white_list[]
Definition: validate.c:30
static BOOL GetBuiltinAdminGroupName(WCHAR *name, DWORD nlen)
Definition: validate.c:128
#define malloc
Definition: cmocka.c:1795
static int OptionLookup(const WCHAR *name, const WCHAR *white_list[])
Definition: validate.c:108
static BOOL LookupSID(const WCHAR *name, PSID sid, DWORD sid_size)
Definition: validate.c:244
#define M_SYSERR
Definition: service.h:47
#define MAX_NAME
Definition: service.h:64
#define free
Definition: cmocka.c:1850
bool openvpn_swprintf(wchar_t *const str, const size_t size, const wchar_t *const format,...)
Definition: buffer.c:321
Definition: argv.h:35
static BOOL CheckConfigPath(const WCHAR *workdir, const WCHAR *fname, const settings_t *s)
Definition: validate.c:62
BOOL IsAuthorizedUser(PSID sid, const HANDLE token, const WCHAR *ovpn_admin_group)
Definition: validate.c:160