summaryrefslogtreecommitdiff
path: root/src/openvpnserv/validate.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/openvpnserv/validate.c')
-rw-r--r--src/openvpnserv/validate.c178
1 files changed, 131 insertions, 47 deletions
diff --git a/src/openvpnserv/validate.c b/src/openvpnserv/validate.c
index c9c3855..f6a97e9 100644
--- a/src/openvpnserv/validate.c
+++ b/src/openvpnserv/validate.c
@@ -16,10 +16,9 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License
- * along with this program (see the file COPYING included with this
- * distribution); if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "validate.h"
@@ -49,6 +48,9 @@ static const WCHAR *white_list[] =
NULL /* last value */
};
+static BOOL IsUserInGroup(PSID sid, const PTOKEN_GROUPS groups, const WCHAR *group_name);
+static PTOKEN_GROUPS GetTokenGroups(const HANDLE token);
+
/*
* Check workdir\fname is inside config_dir
* The logic here is simple: we may reject some valid paths if ..\ is in any of the strings
@@ -147,21 +149,16 @@ GetBuiltinAdminGroupName(WCHAR *name, DWORD nlen)
/*
* Check whether user is a member of Administrators group or
- * the group specified in s->ovpn_admin_group
+ * the group specified in ovpn_admin_group
*/
BOOL
-IsAuthorizedUser(SID *sid, settings_t *s)
+IsAuthorizedUser(PSID sid, const HANDLE token, const WCHAR *ovpn_admin_group)
{
- LOCALGROUP_USERS_INFO_0 *groups = NULL;
- DWORD nread;
- DWORD nmax;
- WCHAR *tmp = NULL;
const WCHAR *admin_group[2];
WCHAR username[MAX_NAME];
WCHAR domain[MAX_NAME];
WCHAR sysadmin_group[MAX_NAME];
- DWORD err, len = MAX_NAME;
- int i;
+ DWORD len = MAX_NAME;
BOOL ret = FALSE;
SID_NAME_USE sid_type;
@@ -169,17 +166,9 @@ IsAuthorizedUser(SID *sid, settings_t *s)
if (!LookupAccountSidW(NULL, sid, username, &len, domain, &len, &sid_type))
{
MsgToEventLog(M_SYSERR, TEXT("LookupAccountSid"));
- goto out;
- }
-
- /* Get an array of groups the user is member of */
- err = NetUserGetLocalGroups(NULL, username, 0, LG_INCLUDE_INDIRECT, (LPBYTE *) &groups,
- MAX_PREFERRED_LENGTH, &nread, &nmax);
- if (err && err != ERROR_MORE_DATA)
- {
- SetLastError(err);
- MsgToEventLog(M_SYSERR, TEXT("NetUserGetLocalGroups"));
- goto out;
+ /* not fatal as this is now used only for logging */
+ username[0] = '\0';
+ domain[0] = '\0';
}
if (GetBuiltinAdminGroupName(sysadmin_group, _countof(sysadmin_group)))
@@ -192,41 +181,136 @@ IsAuthorizedUser(SID *sid, settings_t *s)
/* use the default value */
admin_group[0] = SYSTEM_ADMIN_GROUP;
}
+ admin_group[1] = ovpn_admin_group;
-#ifdef UNICODE
- admin_group[1] = s->ovpn_admin_group;
-#else
- tmp = NULL;
- len = MultiByteToWideChar(CP_UTF8, 0, s->ovpn_admin_group, -1, NULL, 0);
- if (len == 0 || (tmp = malloc(len*sizeof(WCHAR))) == NULL)
+ PTOKEN_GROUPS token_groups = GetTokenGroups(token);
+ for (int i = 0; i < 2; ++i)
{
- MsgToEventLog(M_SYSERR, TEXT("Failed to convert admin group name to WideChar"));
- goto out;
+ ret = IsUserInGroup(sid, token_groups, admin_group[i]);
+ if (ret)
+ {
+ MsgToEventLog(M_INFO, TEXT("Authorizing user '%s@%s' by virtue of membership in group '%s'"),
+ username, domain, admin_group[i]);
+ goto out;
+ }
}
- MultiByteToWideChar(CP_UTF8, 0, s->ovpn_admin_group, -1, tmp, len);
- admin_group[1] = tmp;
-#endif
- /* Check if user's groups include any of the admin groups */
- for (i = 0; i < nread; i++)
+out:
+ free(token_groups);
+ return ret;
+}
+
+/**
+ * Get a list of groups in token.
+ * Returns a pointer to TOKEN_GROUPS struct or NULL on error.
+ * The caller should free the returned pointer.
+ */
+static PTOKEN_GROUPS
+GetTokenGroups(const HANDLE token)
+{
+ PTOKEN_GROUPS groups = NULL;
+ DWORD buf_size = 0;
+
+ if (!GetTokenInformation(token, TokenGroups, groups, buf_size, &buf_size)
+ && GetLastError() == ERROR_INSUFFICIENT_BUFFER)
+ {
+ groups = malloc(buf_size);
+ }
+ if (!groups)
+ {
+ MsgToEventLog(M_SYSERR, L"GetTokenGroups");
+ }
+ else if (!GetTokenInformation(token, TokenGroups, groups, buf_size, &buf_size))
{
- if (wcscmp(groups[i].lgrui0_name, admin_group[0]) == 0
- || wcscmp(groups[i].lgrui0_name, admin_group[1]) == 0
- )
+ MsgToEventLog(M_SYSERR, L"GetTokenInformation");
+ free(groups);
+ }
+ return groups;
+}
+
+/*
+ * Find SID from name
+ *
+ * On input sid buffer should have space for at least sid_size bytes.
+ * Returns true on success, false on failure.
+ * Suggest: in caller allocate sid to hold SECURITY_MAX_SID_SIZE bytes
+ */
+static BOOL
+LookupSID(const WCHAR *name, PSID sid, DWORD sid_size)
+{
+ SID_NAME_USE su;
+ WCHAR domain[MAX_NAME];
+ DWORD dlen = _countof(domain);
+
+ if (!LookupAccountName(NULL, name, sid, &sid_size, domain, &dlen, &su))
+ {
+ return FALSE; /* not fatal as the group may not exist */
+ }
+ return TRUE;
+}
+
+/**
+ * User is in group if the token groups contain the SID of the group
+ * of if the user is a direct member of the group. The latter check
+ * catches dynamic changes in group membership in the local user
+ * database not reflected in the token.
+ * If token_groups or sid is NULL the corresponding check is skipped.
+ *
+ * Using sid and list of groups in token avoids reference to domains so that
+ * this could be completed without access to a Domain Controller.
+ *
+ * Returns true if the user is in the group, false otherwise.
+ */
+static BOOL
+IsUserInGroup(PSID sid, const PTOKEN_GROUPS token_groups, const WCHAR *group_name)
+{
+ BOOL ret = FALSE;
+ DWORD_PTR resume = 0;
+ DWORD err;
+ BYTE grp_sid[SECURITY_MAX_SID_SIZE];
+ int nloop = 0; /* a counter used to not get stuck in the do .. while() */
+
+ /* first check in the token groups */
+ if (token_groups && LookupSID(group_name, (PSID) grp_sid, _countof(grp_sid)))
+ {
+ for (DWORD i = 0; i < token_groups->GroupCount; ++i)
{
- MsgToEventLog(M_INFO, TEXT("Authorizing user %s by virtue of membership in group %s"),
- username, groups[i].lgrui0_name);
- ret = TRUE;
- break;
+ if (EqualSid((PSID) grp_sid, token_groups->Groups[i].Sid))
+ {
+ return TRUE;
+ }
}
}
-out:
- if (groups)
+ /* check user's SID is a member of the group */
+ if (!sid)
+ {
+ return FALSE;
+ }
+ do
+ {
+ DWORD nread, nmax;
+ LOCALGROUP_MEMBERS_INFO_0 *members = NULL;
+ err = NetLocalGroupGetMembers(NULL, group_name, 0, (LPBYTE *) &members,
+ MAX_PREFERRED_LENGTH, &nread, &nmax, &resume);
+ if ((err != NERR_Success && err != ERROR_MORE_DATA))
+ {
+ break;
+ }
+ /* If a match is already found, ret == TRUE and the loop is skipped */
+ for (int i = 0; i < nread && !ret; ++i)
+ {
+ ret = EqualSid(members[i].lgrmi0_sid, sid);
+ }
+ NetApiBufferFree(members);
+ /* MSDN says the lookup should always iterate until err != ERROR_MORE_DATA */
+ } while (err == ERROR_MORE_DATA && nloop++ < 100);
+
+ if (err != NERR_Success && err != NERR_GroupNotFound)
{
- NetApiBufferFree(groups);
+ SetLastError(err);
+ MsgToEventLog(M_SYSERR, TEXT("In NetLocalGroupGetMembers for group '%s'"), group_name);
}
- free(tmp);
return ret;
}