openvpn-gui/env_set.c

370 lines
9.2 KiB
C

/*
* OpenVPN-GUI -- A Windows GUI for OpenVPN.
*
* Copyright (C) 2017 Selva Nair <selva.nair@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* 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
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <windows.h>
#include <wchar.h>
#include "main.h"
#include "options.h"
#include "misc.h"
#include "openvpn.h"
#include "env_set.h"
struct env_item {
wchar_t *nameval;
struct env_item *next;
};
/* To match with openvpn we accept only :ALPHA:, :DIGIT: or '_' in names */
BOOL
is_valid_env_name(const char *name)
{
if (name[0] == '\0')
{
return false;
}
while (*name)
{
const char c = *name;
if (!isalnum(c) && c != '_')
{
return false;
}
name++;
}
return true;
}
/* Compare two strings of the form name1=val and name2=val
* return value is -ve, 0 or +ve for name1 less than, equal
* to or greater than name2.
* The comparison is locale-independent and case insensitive.
* If '=' is missing the whole string is used as name.
*/
int
env_name_compare(const wchar_t *nameval1, const wchar_t *nameval2)
{
size_t len1 = wcscspn(nameval1, L"=");
size_t len2 = wcscspn(nameval2, L"=");
/* For env variable names we want locale independent case-insensitive
* unicode string comparsion. Use CompareStringOrdinal following
* https://msdn.microsoft.com/en-us/library/windows/desktop/dd318144(v=vs.85).aspx
*/
BOOL ignore_case = true;
int cmp = CompareStringOrdinal(nameval1, (int)len1, nameval2, (int)len2, ignore_case);
return cmp - 2; /* -2 to bring the result match strcmp semantics */
}
static void
env_item_free(struct env_item *item)
{
free(item->nameval);
free(item);
}
/* Delete an env var item with matching name: if name is of the
* form xxx=yyy, only the part xxx is used for matching.
* Returns the head of the list.
*/
static struct env_item *
env_item_del(struct env_item *head, const wchar_t *name)
{
struct env_item *item, *prev = NULL;
if (!name) return head;
for (item = head; item; item = item->next)
{
if (env_name_compare(name, item->nameval) == 0)
{
/* matching item found */
if (prev)
prev->next = item->next;
else /* head is going to be deleted */
head = item->next;
env_item_free(item);
break;
}
prev = item;
continue;
}
return head;
}
/* Insert a env var item to an env set: any existing item
* with same name is replaced by the new entry. Else
* the item is added at an alphabetically sorted location.
* Returns the new head of the list.
*/
struct env_item *
env_item_insert(struct env_item *head, struct env_item *item)
{
struct env_item *tmp, *prev = NULL;
int cmp = -1;
/* find location of new item in sorted order: head == NULL is ok */
for (tmp = head; tmp; tmp = tmp->next)
{
cmp = env_name_compare(item->nameval, tmp->nameval);
if (cmp <= 0) /* found the position to add */
{
break;
}
prev = tmp;
continue;
}
if (cmp == 0) /* name already set -- replace */
{
item->next = tmp->next;
env_item_free(tmp);
}
else /* add item at this point */
{
item->next = tmp;
}
if (prev)
prev->next = item;
else
head = item;
return head;
}
void
env_item_del_all(struct env_item *head)
{
struct env_item *next;
for ( ; head; head = next)
{
next = head->next;
env_item_free(head);
}
}
/* convenience functions for create, add and delete an item
* given nameval as a utf8 string.
*/
/* Create a new env item given nameval name=val */
struct env_item *
env_item_new_utf8(const char *nameval)
{
struct env_item *new = malloc(sizeof(struct env_item));
if (!new)
{
return NULL;
}
new->nameval = Widen(nameval);
new->next = NULL;
if (!new->nameval)
{
free(new);
return NULL;
}
return new;
}
/* Insert an env item to the set given nameval: name=val.
* Returns new head of the list.
*/
static struct env_item*
env_item_insert_utf8(struct env_item *head, const char *nameval)
{
struct env_item *item = env_item_new_utf8(nameval);
if (!item)
return head;
return env_item_insert(head, item);
}
/* Delete an env item from the list with matching name. Returns the
* new head of the list. If name is given as name=val, only the
* name part is used for matching.
*/
static struct env_item *
env_item_del_utf8(struct env_item *head, const char *name)
{
wchar_t *wname = Widen(name);
if (wname)
{
head = env_item_del(head, wname);
free(wname);
}
return head;
}
/*
* Make an env block by merging items in es to the process env block
* retaining alphabetical order as necessary on Windows.
* Returns NULL on error or a newly allocated string that may be passed
* to CreateProcess as the env block. The caller must free the returned
* pointer.
*/
wchar_t *
merge_env_block(const struct env_item *es)
{
size_t len = 0;
/* e should be treated as read-only though cannot be defined as const
* due to the need to call FreeEnvironmentStrings in the end.
*/
wchar_t *e = GetEnvironmentStringsW();
const struct env_item *item;
const wchar_t *pe;
if (!e)
{
return NULL;
}
for (pe = e; *pe; pe += wcslen(pe)+1)
{
;
}
len = (pe + 1 - e); /* including the extra '\0' at the end */
for(item = es; item; item = item->next)
{
len += wcslen(item->nameval) + 1;
}
wchar_t *env = malloc(sizeof(wchar_t)*len);
if (!env)
{
/* no memory -- return NULL */
FreeEnvironmentStringsW(e);
return NULL;
}
wchar_t *p = env;
item = es;
pe = e;
len = wcslen(pe) + 1;
/* Merge two sroted collections env set and process env.
* In case of duplicates the env set entry replaces that in the
* process env.
*/
while (item && *pe)
{
int cmp = env_name_compare(item->nameval, pe);
if (cmp <= 0) /* add entry from env set */
{
wcscpy(p, item->nameval);
p += wcslen(item->nameval) + 1;
item = item->next;
}
else /* add entry from process env */
{
wcscpy(p, pe);
p += len;
}
if (cmp >= 0) /* pe was added (cmp >0) or has to be skipped (cmp==0) */
{
pe += len;
if (*pe) /* update len */
len = wcslen(pe) + 1;
}
}
/* Add any remaining entries -- either item or *pe is NULL at this point.
* So only one of the two following loops will run.
*/
for ( ; item; item = item->next)
{
wcscpy(p, item->nameval);
p += wcslen(item->nameval) + 1;
}
for ( ; *pe; pe += len, p += len)
{
wcscpy(p, pe);
len = wcslen(pe) + 1;
}
*p = L'\0';
FreeEnvironmentStringsW(e);
return env;
}
/* Expect "setenv name value" and add name=value
* to a private env set with name prefixed by OPENVPN_.
* If value is missing we delete name from the env set.
*/
void
process_setenv(connection_t *c, UNUSED time_t timestamp, const char *msg)
{
const char *prefix = "OPENVPN_";
char *p;
char *nameval;
if (!strbegins(msg, "setenv "))
return;
msg += strlen("setenv "); /* character following "setenv" */
msg += strspn(msg, " \t"); /* skip leading space */
if (msg[0] == '\0')
{
WriteStatusLog(c, L"GUI> ", L"Error: Name empty in echo setenv", false);
return;
}
nameval = malloc(strlen(prefix) + strlen(msg) + 1);
if (!nameval)
{
WriteStatusLog(c, L"GUI> ", L"Error: Out of memory for adding env var", false);
return;
}
strcpy(nameval, prefix);
strcat(nameval, msg);
if ((p = strchr(nameval, ' ')) != NULL)
{
*p = '\0'; /* temporary null termination */
if (is_valid_env_name(nameval))
{
*p = '=';
c->es = env_item_insert_utf8(c->es, nameval);
}
else
{
*p = ' ';
WriteStatusLog(c, L"GUI> ", L"Error: empty or illegal name in echo setenv", false);
}
}
/* if only name is specified and valid, delete the value from env set */
else if (is_valid_env_name(nameval))
{
c->es = env_item_del_utf8(c->es, nameval);
}
free(nameval); /* env_item keeps a private wide string copy */
}