src/claim/main.c
Rama master · réplica de lectura del repositorio original.
// SPDX-License-Identifier: GPL-3.0-or-later
#define UNICODE
#define _UNICODE
#include <windows.h>
#include <shellapi.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <signal.h>
#include "main.h"
LPWSTR token = NULL;
LPWSTR room = NULL;
LPWSTR proxy = NULL;
LPWSTR url = NULL;
LPWSTR extPath = NULL;
LPWSTR *argv = NULL;
char *aToken = NULL;
char *aRoom = NULL;
char *aProxy = NULL;
char *aURL = NULL;
int insecure = 0;
// The MSI runs us as a deferred, non-impersonated custom action: that lives in session 0, where a
// window or a message box is invisible to the user and never gets closed, hanging the installation.
// Default to the safe mode and only enable dialogs once we know no arguments were passed.
int nd_claim_interactive = 0;
LPWSTR netdata_claim_get_formatted_message(LPWSTR pMessage, ...)
{
LPWSTR pBuffer = NULL;
va_list args = NULL;
va_start(args, pMessage);
FormatMessage(FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ALLOCATE_BUFFER, pMessage, 0, 0, (LPWSTR)&pBuffer,
0, &args);
va_end(args);
return pBuffer;
}
// Common Functions
void netdata_claim_error_exit(wchar_t *function, int code)
{
DWORD error = GetLastError();
if (nd_claim_interactive) {
LPWSTR pMessage = L"The function %1 failed with error %2.";
LPWSTR pBuffer = netdata_claim_get_formatted_message(pMessage, function, error);
if (pBuffer) {
MessageBoxW(NULL, pBuffer, L"Error", MB_OK|MB_ICONERROR);
LocalFree(pBuffer);
}
}
// Report through a stable exit code instead of the raw Win32 error: the installer logs the
// custom action's return value, and Win32 codes overlap the classes callers need to tell apart.
ExitProcess((UINT)code);
}
// Installer fields and command lines routinely carry pasted leading/trailing whitespace. Left in
// place it ends up inside claim.conf and Netdata Cloud rejects the credentials.
static void netdata_claim_trim(char *s)
{
if (!s)
return;
char *start = s;
while (*start && isspace((unsigned char)*start))
start++;
if (start != s)
memmove(s, start, strlen(start) + 1);
size_t len = strlen(s);
while (len && isspace((unsigned char)s[len - 1]))
s[--len] = '\0';
}
/**
* Parse Args
*
* Parse the command line given by the installer or by a script.
*
* @param argc number of arguments
* @param argv A pointer for all arguments given
*
* @return 1 when a usable configuration was parsed, 0 otherwise.
*/
int nd_claim_parse_args(int argc, LPWSTR *argv)
{
int i;
for (i = 1 ; i < argc; i++) {
// We are working with Microsoft, thus it does not make sense wait for only smallcase
if(wcscasecmp(L"/T", argv[i]) == 0) {
if (argc <= i + 1)
continue;
i++;
token = argv[i];
}
if(wcscasecmp(L"/R", argv[i]) == 0) {
if (argc <= i + 1)
continue;
i++;
room = argv[i];
}
if(wcscasecmp(L"/P", argv[i]) == 0) {
if (argc <= i + 1)
continue;
i++;
proxy = argv[i];
}
if(wcscasecmp(L"/F", argv[i]) == 0) {
if (argc <= i + 1)
continue;
i++;
extPath = argv[i];
}
if(wcscasecmp(L"/U", argv[i]) == 0) {
if (argc <= i + 1)
continue;
i++;
url = argv[i];
}
if(wcscasecmp(L"/I", argv[i]) == 0) {
if (argc <= i + 1)
continue;
i++;
size_t length = wcslen(argv[i]) + 1;
char *tmp = calloc(sizeof(char), length);
if (!tmp)
ExitProcess(ND_CLAIM_INTERNAL);
netdata_claim_convert_str(tmp, argv[i], length);
// An empty value is the installer's "unchecked" state and parses as disabled.
insecure = atoi(tmp);
free(tmp);
}
}
// A token is the only mandatory field. Rooms are optional here exactly as they are for the
// Linux claiming script and for NETDATA_CLAIM_ROOMS: the Agent then lands in the Space's
// default room.
if (!token)
return 0;
return 1;
}
static int netdata_claim_prepare_strings()
{
if (!token)
return -1;
size_t length = wcslen(token) + 1;
aToken = calloc(sizeof(char), length);
if (!aToken)
return -1;
netdata_claim_convert_str(aToken, token, length);
netdata_claim_trim(aToken);
if (room) {
length = wcslen(room) + 1;
aRoom = calloc(sizeof(char), length);
if (!aRoom)
return -1;
netdata_claim_convert_str(aRoom, room, length);
netdata_claim_trim(aRoom);
}
if (proxy) {
length = wcslen(proxy) + 1;
aProxy = calloc(sizeof(char), length);
if (!aProxy)
return -1;
netdata_claim_convert_str(aProxy, proxy, length);
netdata_claim_trim(aProxy);
}
if (url) {
length = wcslen(url) + 1;
aURL = calloc(sizeof(char), length);
if (!aURL)
return -1;
netdata_claim_convert_str(aURL, url, length);
netdata_claim_trim(aURL);
}
return 0;
}
static void netdata_claim_exit_callback(int signal)
{
if (aToken) {
free(aToken);
aToken = NULL;
}
if (aRoom) {
free(aRoom);
aRoom = NULL;
}
if (aProxy) {
free(aProxy);
aProxy = NULL;
}
if (aURL) {
free(aURL);
aURL = NULL;
}
if (argv) {
LocalFree(argv);
argv = NULL;
token = NULL;
room = NULL;
proxy = NULL;
url = NULL;
extPath = NULL;
}
if (signal)
ExitProcess((UINT)signal);
}
static inline int netdata_claim_prepare_data(char *out, size_t length)
{
// Leave unset optional keys commented out. An empty "proxy" would override the "env" default the
// Agent applies when the key is absent, and a commented "rooms" documents that the Space's
// default room is used instead of looking like a lost value.
char *roomsLabel = (aRoom && *aRoom) ? "rooms = " : "# rooms = ";
char *roomsValue = (aRoom && *aRoom) ? aRoom : "";
char *proxyLabel = (aProxy && *aProxy) ? "proxy = " : "# proxy = ";
char *proxyValue = (aProxy && *aProxy) ? aProxy : "";
char *urlValue = (aURL && *aURL) ? aURL : "https://app.netdata.cloud";
return snprintf(out,
length,
"[global]\n url = %s\n token = %s\n %s%s\n %s%s\n insecure = %s\n",
urlValue,
aToken,
roomsLabel,
roomsValue,
proxyLabel,
proxyValue,
(insecure) ? "yes" : "no"
);
}
static int netdata_claim_get_path(char *path)
{
if (extPath) {
size_t length = wcslen(extPath) + 1;
if (length >= WINDOWS_MAX_PATH)
return -1;
netdata_claim_convert_str(path, extPath, length);
return 0;
}
char *usrPath = { "\\usr\\bin" };
DWORD length = GetCurrentDirectoryA(WINDOWS_MAX_PATH, path);
if (!length) {
return -1;
}
if (strstr(path, usrPath)) {
length -= 7;
path[length] = '\0';
}
return 0;
}
static int netdata_claim_write_config(char *path)
{
// Only an empty token is rejected. Netdata Cloud owns the token format, so validating its length
// here just means a token the installer already accepted disappears without a trace; letting the
// Agent attempt the claim and log the rejection is diagnosable.
if (!aToken || !*aToken)
return ND_CLAIM_BAD_ARGS;
char configPath[WINDOWS_MAX_PATH + 1];
char data[WINDOWS_MAX_PATH + 1];
char *filename;
if (!extPath) {
// Refuse a truncated path instead of creating a file under a shortened name that the Agent
// would never read.
int pathLength = snprintf(configPath, sizeof(configPath), "%s\\etc\\netdata\\claim.conf", path);
if (pathLength < 0 || (size_t)pathLength >= sizeof(configPath))
return ND_CLAIM_INTERNAL;
filename = configPath;
} else {
filename = path;
}
int length = netdata_claim_prepare_data(data, WINDOWS_MAX_PATH);
if (length < 0 || length >= WINDOWS_MAX_PATH)
return ND_CLAIM_INTERNAL;
HANDLE hf = CreateFileA(filename, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hf == INVALID_HANDLE_VALUE)
netdata_claim_error_exit(L"CreateFileA", ND_CLAIM_WRITE_FAILED);
DWORD written = 0;
BOOL ret = WriteFile(hf, data, (DWORD)length, &written, NULL);
CloseHandle(hf);
if (!ret || (DWORD)length != written)
return ND_CLAIM_WRITE_FAILED;
return ND_CLAIM_OK;
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
signal(SIGABRT, netdata_claim_exit_callback);
signal(SIGINT, netdata_claim_exit_callback);
signal(SIGTERM, netdata_claim_exit_callback);
int argc = 0;
argv = CommandLineToArgvW(GetCommandLineW(), &argc);
if (!argv)
netdata_claim_error_exit(L"CommandLineToArgvW", ND_CLAIM_INTERNAL);
// Any argument means the installer or a script started us. In that mode we must never create a
// window and never block on a message box, so bad input is reported through the exit code only.
nd_claim_interactive = (argc <= 1);
int ret;
if (nd_claim_interactive) {
ret = netdata_claim_window_loop(hInstance, nCmdShow);
} else if (!nd_claim_parse_args(argc, argv)) {
ret = ND_CLAIM_BAD_ARGS;
} else if (netdata_claim_prepare_strings()) {
// The token is already known to be present, so the only failure left here is an allocation.
ret = ND_CLAIM_INTERNAL;
} else {
char basePath[WINDOWS_MAX_PATH];
ret = (netdata_claim_get_path(basePath)) ? ND_CLAIM_INTERNAL : netdata_claim_write_config(basePath);
}
netdata_claim_exit_callback(0);
return ret;
}