ogserver/src/client.c

1404 lines
38 KiB
C

/*
* Copyright (C) 2020-2021 Soleta Networks <info@soleta.eu>
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*/
#include "ogAdmServer.h"
#include "cfg.h"
#include "dbi.h"
#include "utils.h"
#include "list.h"
#include "rest.h"
#include "json.h"
#include <syslog.h>
#include <sys/ioctl.h>
#include <ifaddrs.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <jansson.h>
#include <time.h>
#define OG_LEGACY_OGAGENT_LOG_FILE "/opt/opengnsys/log/ogagent.log"
#define OG_CLIENT_SESSION_EVENT_LOGIN "logged in"
#define OG_CLIENT_SESSION_EVENT_LOGOUT "logged out"
#define OG_CLIENT_SESSION_OS_LINUX "Linux"
#define OG_CLIENT_SESSION_OS_WINDOWS "Windows"
#define OG_CLIENT_SESSION_TIMEDATE_LEN 20
static struct {
const char *name;
uint32_t id;
} og_fs[] = {
{ "EMPTY", 1 },
{ "CACHE", 2 },
{ "BTRFS", 3 },
{ "EXT3", 5 },
{ "EXT4", 6 },
{ "FAT32", 9 },
{ "HFS", 10 },
{ "HFSPLUS", 11 },
{ "NTFS", 13 },
{ "EXFAT", 18 },
{ "LINUX-SWAP", 19 },
{ "SWAP", 22 },
{ NULL, 0 },
};
static uint32_t get_filesystem_id(const char *fs_name)
{
uint32_t i;
if (strlen(fs_name) == 0)
return 0;
for (i = 0; og_fs[i].name != NULL; i++) {
if (!strcmp(og_fs[i].name, fs_name))
return og_fs[i].id;
}
return 0;
}
static void og_status_session_log(const struct og_client *cli,
const char *type, const char* user,
const char *os)
{
char date[OG_CLIENT_SESSION_TIMEDATE_LEN];
char client_ip[INET_ADDRSTRLEN];
time_t now;
FILE *fp;
time(&now);
strftime(date, OG_CLIENT_SESSION_TIMEDATE_LEN, "%FT%T", gmtime(&now));
inet_ntop(AF_INET, &(cli->addr.sin_addr), client_ip, INET_ADDRSTRLEN);
fp = fopen(OG_LEGACY_OGAGENT_LOG_FILE, "a");
if (fp) {
fprintf(fp, "%s: User %s: ip=%s, user=%s, lang=en, os=%s:%s.\n",
date, type, client_ip, user, os, os);
fclose(fp);
}
}
static int og_status_session_start(struct og_client *cli, const char *user)
{
switch (cli->status) {
case OG_CLIENT_STATUS_LINUX:
cli->status = OG_CLIENT_STATUS_LINUX_SESSION;
og_status_session_log(cli, OG_CLIENT_SESSION_EVENT_LOGIN, user,
OG_CLIENT_SESSION_OS_LINUX);
break;
case OG_CLIENT_STATUS_WIN:
cli->status = OG_CLIENT_STATUS_WIN_SESSION;
og_status_session_log(cli, OG_CLIENT_SESSION_EVENT_LOGIN, user,
OG_CLIENT_SESSION_OS_WINDOWS);
break;
default:
syslog(LOG_ERR, "%s:%d: invalid session start for status %d\n",
__FILE__, __LINE__, cli->status);
return -1;
}
return 0;
}
static int og_status_session_stop(struct og_client *cli, const char *user)
{
switch (cli->status) {
case OG_CLIENT_STATUS_WIN_SESSION:
cli->status = OG_CLIENT_STATUS_WIN;
og_status_session_log(cli, OG_CLIENT_SESSION_EVENT_LOGOUT, user,
OG_CLIENT_SESSION_OS_WINDOWS);
break;
case OG_CLIENT_STATUS_LINUX_SESSION:
cli->status = OG_CLIENT_STATUS_LINUX;
og_status_session_log(cli, OG_CLIENT_SESSION_EVENT_LOGOUT, user,
OG_CLIENT_SESSION_OS_LINUX);
break;
default:
syslog(LOG_ERR, "%s:%d: invalid session stop for status %d\n",
__FILE__, __LINE__, cli->status);
return -1;
}
return 0;
}
static int og_resp_early_hints(struct og_client *cli, json_t *data)
{
const char *event, *action, *user;
const char *key;
json_t *value;
int err = 0;
if (json_typeof(data) != JSON_OBJECT)
return -1;
json_object_foreach(data, key, value) {
if (!strcmp(key, "event")) {
err = og_json_parse_string(value, &event);
if (err < 0)
return err;
} else if (!strcmp(key, "action")) {
err = og_json_parse_string(value, &action);
if (err < 0)
return err;
} else if (!strcmp(key, "user")) {
err = og_json_parse_string(value, &user);
if (err < 0)
return err;
}
}
syslog(LOG_INFO, "Received event %s %s %s\n", event, action, user);
if (strncmp(event, "session", strlen("session")))
return -1;
if (!strncmp(action, "start", strlen("start")))
return og_status_session_start(cli, user);
if (!strncmp(action, "stop", strlen("stop")))
return og_status_session_stop(cli, user);
syslog(LOG_ERR, "Invalid action for event %s %s %s\n", event, action, user);
return -1;
}
static int og_resp_shell_run(struct og_client *cli, json_t *data)
{
const char *cmd = NULL, *output = NULL;
uint32_t retcode = 0;
const char *key;
json_t *value;
int err = -1;
if (json_typeof(data) != JSON_OBJECT)
return -1;
json_object_foreach(data, key, value) {
if (!strcmp(key, "cmd")) {
err = og_json_parse_string(value, &cmd);
if (err < 0)
return err;
} else if (!strcmp(key, "out")) {
err = og_json_parse_string(value, &output);
if (err < 0)
return err;
} else if (!strcmp(key, "retcode")) {
err = og_json_parse_uint(value, &retcode);
if (err < 0)
return err;
}
}
if (!cmd || !output) {
syslog(LOG_ERR, "%s:%d: malformed json response\n",
__FILE__, __LINE__);
return -1;
}
free((void *)cli->shell.cmd);
free((void *)cli->shell.output);
cli->shell.tstamp = time(NULL);
cli->shell.cmd = strdup(cmd);
cli->shell.output = strdup(output);
cli->shell.retcode = retcode;
return 0;
}
struct og_computer_legacy {
char center[OG_DB_INT_MAXLEN + 1];
char id[OG_DB_INT_MAXLEN + 1];
char hardware[8192];
};
static int og_resp_hardware(json_t *data, struct og_client *cli)
{
struct og_computer_legacy legacy = {};
struct og_computer computer = {};
const char *hardware = NULL;
struct og_dbi *dbi;
const char *key;
json_t *value;
int err = 0;
bool res;
if (json_typeof(data) != JSON_OBJECT)
return -1;
json_object_foreach(data, key, value) {
if (!strcmp(key, "hardware")) {
err = og_json_parse_string(value, &hardware);
if (err < 0)
return -1;
}
}
if (!hardware) {
syslog(LOG_ERR, "malformed response json\n");
return -1;
}
dbi = og_dbi_open(&ogconfig.db);
if (!dbi) {
syslog(LOG_ERR, "cannot open connection database (%s:%d)\n",
__func__, __LINE__);
return -1;
}
err = og_dbi_get_computer_info(dbi, &computer, cli->addr.sin_addr);
if (err < 0) {
og_dbi_close(dbi);
return -1;
}
snprintf(legacy.center, sizeof(legacy.center), "%d", computer.center);
snprintf(legacy.id, sizeof(legacy.id), "%d", computer.id);
snprintf(legacy.hardware, sizeof(legacy.hardware), "%s", hardware);
res = actualizaHardware(dbi, legacy.hardware, legacy.id, computer.name,
legacy.center);
og_dbi_close(dbi);
if (!res) {
syslog(LOG_ERR, "Problem updating client configuration\n");
return -1;
}
return 0;
}
struct og_software_legacy {
char software[32768];
char center[OG_DB_INT_MAXLEN + 1];
char part[OG_DB_SMALLINT_MAXLEN + 1];
char id[OG_DB_INT_MAXLEN + 1];
};
static int og_resp_software(json_t *data, struct og_client *cli)
{
struct og_software_legacy legacy = {};
struct og_computer computer = {};
const char *partition = NULL;
const char *software = NULL;
struct og_dbi *dbi;
const char *key;
json_t *value;
int err = 0;
bool res;
if (json_typeof(data) != JSON_OBJECT)
return -1;
json_object_foreach(data, key, value) {
if (!strcmp(key, "software"))
err = og_json_parse_string(value, &software);
else if (!strcmp(key, "partition"))
err = og_json_parse_string(value, &partition);
if (err < 0)
return -1;
}
if (!software || !partition) {
syslog(LOG_ERR, "malformed response json\n");
return -1;
}
dbi = og_dbi_open(&ogconfig.db);
if (!dbi) {
syslog(LOG_ERR, "cannot open connection database (%s:%d)\n",
__func__, __LINE__);
return -1;
}
err = og_dbi_get_computer_info(dbi, &computer, cli->addr.sin_addr);
if (err < 0) {
og_dbi_close(dbi);
return -1;
}
snprintf(legacy.software, sizeof(legacy.software), "%s", software);
snprintf(legacy.part, sizeof(legacy.part), "%s", partition);
snprintf(legacy.id, sizeof(legacy.id), "%d", computer.id);
snprintf(legacy.center, sizeof(legacy.center), "%d", computer.center);
res = actualizaSoftware(dbi, legacy.software, legacy.part, legacy.id,
computer.name, legacy.center);
og_dbi_close(dbi);
if (!res) {
syslog(LOG_ERR, "Problem updating client configuration\n");
return -1;
}
return 0;
}
#define OG_PARAMS_RESP_REFRESH (OG_PARAM_PART_DISK | \
OG_PARAM_PART_NUMBER | \
OG_PARAM_PART_CODE | \
OG_PARAM_PART_FILESYSTEM | \
OG_PARAM_PART_OS | \
OG_PARAM_PART_SIZE | \
OG_PARAM_PART_USED_SIZE | \
OG_PARAM_PART_FREE_SIZE)
static int og_json_parse_partition_array(json_t *value,
struct og_partition *partitions,
uint32_t *num_partitions)
{
json_t *element;
int i, err;
if (json_typeof(value) != JSON_ARRAY)
return -1;
for (i = 0; i < json_array_size(value) && i < OG_PARTITION_MAX; i++) {
element = json_array_get(value, i);
err = og_json_parse_partition(element, &partitions[i],
OG_PARAMS_RESP_REFRESH);
if (err < 0)
return err;
}
*num_partitions = i;
return 0;
}
static int og_update_cache_info(struct og_dbi *dbi, struct og_cache_data *cache_data, int clientid)
{
struct og_cache_image *cache_image;
const char *msglog;
dbi_result result;
/* Remove old cache image info */
result = dbi_conn_queryf(dbi->conn,
"DELETE FROM cache WHERE clientid=%d;", clientid);
if (!result) {
dbi_conn_error(dbi->conn, &msglog);
syslog(LOG_ERR, "failed to query database (%s:%d) %s\n",
__func__, __LINE__, msglog);
return false;
}
dbi_result_free(result);
/* Add new cache image info */
list_for_each_entry(cache_image, &cache_data->image_list, list) {
result = dbi_conn_queryf(dbi->conn,
"INSERT INTO cache (clientid, imagename, size, checksum)"
"VALUES (%d, '%s', %lu, '%s')",
clientid,
cache_image->name,
cache_image->size,
cache_image->checksum);
if (!result) {
dbi_conn_error(dbi->conn, &msglog);
syslog(LOG_ERR, "failed to query database (%s:%d) %s\n",
__func__, __LINE__, msglog);
return false;
}
dbi_result_free(result);
}
/* Update partition sizes */
result = dbi_conn_queryf(dbi->conn,
"UPDATE ordenadores_particiones SET used_size = %lu, free_size = %lu"
" WHERE idordenador = %d AND codpar = %d",
cache_data->used_size,
cache_data->free_size,
clientid,
202);
if (!result) {
dbi_conn_error(dbi->conn, &msglog);
syslog(LOG_ERR, "failed to query database (%s:%d) %s\n",
__func__, __LINE__, msglog);
return false;
}
dbi_result_free(result);
return 0;
}
static int og_update_boot_entries(struct og_dbi *dbi, struct list_head *boot_entry_list, int client_id)
{
struct og_boot_entry *boot_entry;
const char *msglog;
dbi_result result;
/* Remove old boot entries */
result = dbi_conn_queryf(dbi->conn,
"DELETE FROM boot_entries WHERE client_id=%d;", client_id);
if (!result) {
dbi_conn_error(dbi->conn, &msglog);
syslog(LOG_ERR, "failed to query database (%s:%d) %s\n",
__func__, __LINE__, msglog);
return -1;
}
dbi_result_free(result);
/* Add new boot entries */
list_for_each_entry(boot_entry, boot_entry_list, list) {
result = dbi_conn_queryf(dbi->conn,
"INSERT INTO boot_entries (client_id, name, active, description, entry_order)"
"VALUES (%d, '%s', %d, '%s', %d)",
client_id,
boot_entry->name,
boot_entry->active ? 1 : 0,
boot_entry->description,
boot_entry->order == UINT64_MAX ? -1 : boot_entry->order);
if (!result) {
dbi_conn_error(dbi->conn, &msglog);
syslog(LOG_ERR, "failed to query database (%s:%d) %s\n",
__func__, __LINE__, msglog);
return -1;
}
dbi_result_free(result);
}
return 0;
}
static int og_resp_update_cache(json_t *data, struct og_client *cli)
{
struct og_cache_data cache_data = {};
struct og_computer computer = {};
json_t *value, *cache = NULL;
struct og_dbi *dbi;
const char *key;
int err = 0;
og_cache_data_init(&cache_data);
if (json_typeof(data) != JSON_OBJECT) {
og_cache_data_free(&cache_data);
return -1;
}
json_object_foreach(data, key, value) {
if (!strcmp(key, "cache")) {
err = og_json_parse_cache(value, &cache_data);
cache = value;
}
if (err < 0) {
og_cache_data_free(&cache_data);
return err;
}
}
if (!cache) {
og_cache_data_free(&cache_data);
return 0;
}
dbi = og_dbi_open(&ogconfig.db);
if (!dbi) {
og_cache_data_free(&cache_data);
syslog(LOG_ERR, "cannot open connection database (%s:%d)\n",
__func__, __LINE__);
return -1;
}
err = og_dbi_get_computer_info(dbi, &computer, cli->addr.sin_addr);
if (err < 0) {
og_cache_data_free(&cache_data);
og_dbi_close(dbi);
return -1;
}
err = og_update_cache_info(dbi, &cache_data, computer.id);
if (err < 0) {
og_cache_data_free(&cache_data);
og_dbi_close(dbi);
return -1;
}
og_cache_data_free(&cache_data);
og_dbi_close(dbi);
return 0;
}
struct og_disk {
uint64_t size;
};
struct og_part {
uint32_t filesystem;
uint32_t os;
uint32_t code;
uint64_t size;
uint64_t used_size;
uint64_t free_size;
};
static bool og_update_client_disk_info(struct og_dbi *dbi,
const struct og_client *cli,
int computer_id,
const struct og_partition *disks,
uint32_t num_disks,
const struct og_partition *partitions,
uint32_t num_partitions,
const char *serial_number)
{
struct og_disk cur_disk = {}, reported_disk = {};
struct og_part cur_part = {}, reported_part = {};
dbi_result result, result_update;
int disk_part_len = 0;
char disk_part[1024];
const char *msglog;
int i;
if (serial_number && strlen(serial_number) > 0) {
result = dbi_conn_queryf(dbi->conn,
"UPDATE ordenadores SET numserie='%s'"
" WHERE idordenador=%d AND numserie IS NULL",
serial_number, computer_id);
if (!result) {
dbi_conn_error(dbi->conn, &msglog);
syslog(LOG_ERR, "failed to query database (%s:%d) %s\n",
__func__, __LINE__, msglog);
return false;
}
dbi_result_free(result);
}
for (i = 0; i < num_disks; i++) {
disk_part_len += snprintf(&disk_part[disk_part_len],
sizeof(disk_part) - disk_part_len,
"(%s, 0),", disks[i].disk);
result = dbi_conn_queryf(dbi->conn,
"SELECT tamano "
" FROM ordenadores_particiones"
" WHERE idordenador=%d AND numdisk=%s AND numpar=0",
computer_id, disks[i].disk);
if (!result) {
dbi_conn_error(dbi->conn, &msglog);
syslog(LOG_ERR, "failed to query database (%s:%d) %s\n",
__func__, __LINE__, msglog);
return false;
}
if (!dbi_result_next_row(result)) {
result_update = dbi_conn_queryf(dbi->conn,
"INSERT INTO ordenadores_particiones("
"idordenador, numdisk, numpar, codpar, tamano, uso, "
"idsistemafichero, idnombreso, disk_type, idimagen,"
"used_size, free_size)"
" VALUES(%d,%s,0,0x%s,%s,0,0,0,'%s',0,0,0)",
computer_id,
disks[i].disk,
disks[i].code,
disks[i].size,
disks[i].disk_type);
if (!result_update) {
dbi_conn_error(dbi->conn, &msglog);
syslog(LOG_ERR, "failed to query database (%s:%d) %s\n",
__func__, __LINE__, msglog);
return false;
}
dbi_result_free(result_update);
dbi_result_free(result);
syslog(LOG_INFO, "adding disk %u with size %s to client %s\n",
i, disks[i].size, inet_ntoa(cli->addr.sin_addr));
continue;
}
reported_disk.size = strtoull(disks[i].size, NULL, 0);
cur_disk.size = dbi_result_get_longlong(result, "tamano");
dbi_result_free(result);
result_update = dbi_conn_queryf(dbi->conn,
"UPDATE ordenadores_particiones SET tamano=%s,"
" codpar=%s, disk_type='%s' "
" WHERE idordenador=%d AND numdisk=%s AND numpar=0",
disks[i].size, disks[i].code, disks[i].disk_type, computer_id, disks[i].disk);
if (!result_update) {
dbi_conn_error(dbi->conn, &msglog);
syslog(LOG_ERR, "failed to query database (%s:%d) %s\n",
__func__, __LINE__, msglog);
return false;
}
dbi_result_free(result_update);
if (cur_disk.size != reported_disk.size)
syslog(LOG_INFO, "disk %s in client %s has changed size, before %lu after %lu bytes\n",
disks[i].disk, inet_ntoa(cli->addr.sin_addr), cur_disk.size, reported_disk.size);
continue;
}
for (i = 0; i < num_partitions; i++) {
disk_part_len += snprintf(&disk_part[disk_part_len],
sizeof(disk_part) - disk_part_len,
"(%s, %s),",
partitions[i].disk, partitions[i].number);
result = dbi_conn_queryf(dbi->conn,
"SELECT numdisk, numpar, tamano, idsistemafichero, idnombreso, "
" codpar, used_size, free_size"
" FROM ordenadores_particiones"
" WHERE idordenador=%d AND numdisk=%s AND numpar=%s",
computer_id, partitions[i].disk, partitions[i].number);
if (!result) {
dbi_conn_error(dbi->conn, &msglog);
syslog(LOG_ERR, "failed to query database (%s:%d) %s\n",
__func__, __LINE__, msglog);
return false;
}
reported_part.size = strtoull(partitions[i].size, NULL, 0);
reported_part.code = strtoul(partitions[i].code, NULL, 16);
reported_part.filesystem = get_filesystem_id(partitions[i].filesystem);
reported_part.used_size = partitions[i].used_size;
reported_part.free_size = partitions[i].free_size;
if (og_dbi_get_os_id(dbi, partitions[i].os, &reported_part.os) < 0) {
return false;
}
if (!dbi_result_next_row(result)) {
result_update = dbi_conn_queryf(dbi->conn,
"INSERT INTO ordenadores_particiones("
"idordenador, numdisk, numpar, codpar, tamano, uso, "
"idsistemafichero, idnombreso, idimagen, "
"used_size, free_size)"
" VALUES(%d,%s,%s,0x%d,%s,0,%d,%d,0,%lu,%lu)",
computer_id,
partitions[i].disk,
partitions[i].number,
reported_part.code,
partitions[i].size,
reported_part.filesystem,
reported_part.os,
partitions[i].used_size,
partitions[i].free_size);
if (!result_update) {
dbi_conn_error(dbi->conn, &msglog);
syslog(LOG_ERR, "failed to query database (%s:%d) %s\n",
__func__, __LINE__, msglog);
return false;
}
dbi_result_free(result_update);
dbi_result_free(result);
syslog(LOG_INFO, "new partition %s in disk %s client %s\n",
partitions[i].number, partitions[i].disk, inet_ntoa(cli->addr.sin_addr));
continue;
}
cur_part.size = dbi_result_get_longlong(result, "tamano");
cur_part.code = dbi_result_get_int(result, "codpar");
cur_part.filesystem = dbi_result_get_uint(result, "idsistemafichero");
cur_part.os = dbi_result_get_uint(result, "idnombreso");
cur_part.used_size = dbi_result_get_longlong(result, "used_size");
cur_part.free_size = dbi_result_get_longlong(result, "free_size");
dbi_result_free(result);
if (cur_part.size != reported_part.size ||
cur_part.code != reported_part.code ||
cur_part.filesystem != reported_part.filesystem ||
cur_part.os != reported_part.os) {
result_update = dbi_conn_queryf(dbi->conn,
"UPDATE ordenadores_particiones SET "
" codpar=0x%s,"
" tamano=%s,"
" idsistemafichero=%d,"
" idnombreso=%d,"
" idimagen=0,"
" idperfilsoft=0,"
" fechadespliegue=NULL"
" WHERE idordenador=%d AND numdisk=%s AND numpar=%s",
partitions[i].code, partitions[i].size,
reported_part.filesystem, reported_part.os, computer_id,
partitions[i].disk, partitions[i].number);
if (!result_update) {
dbi_conn_error(dbi->conn, &msglog);
syslog(LOG_ERR, "failed to query database (%s:%d) %s\n",
__func__, __LINE__, msglog);
return false;
}
dbi_result_free(result_update);
syslog(LOG_INFO, "Disk %s partition %s in client %s has changed\n",
partitions[i].disk, partitions[i].number, inet_ntoa(cli->addr.sin_addr));
continue;
}
if (cur_part.used_size == reported_part.used_size)
continue;
result_update = dbi_conn_queryf(dbi->conn,
"UPDATE ordenadores_particiones SET "
" uso=0, used_size=%lu, free_size=%lu"
" WHERE idordenador=%d AND numdisk=%s AND numpar=%s",
partitions[i].used_size, partitions[i].free_size, computer_id,
partitions[i].disk, partitions[i].number);
if (!result_update) {
dbi_conn_error(dbi->conn, &msglog);
syslog(LOG_ERR, "failed to query database (%s:%d) %s\n",
__func__, __LINE__, msglog);
return false;
}
dbi_result_free(result_update);
syslog(LOG_INFO, "partition usage in disk %s partition %s client %s has changed\n",
partitions[i].disk, partitions[i].number, inet_ntoa(cli->addr.sin_addr));
}
/* remove trailing comma */
disk_part[disk_part_len - 1] = '\0';
result_update = dbi_conn_queryf(dbi->conn,
"DELETE FROM ordenadores_particiones WHERE idordenador=%d AND (numdisk, numpar) NOT IN (%s)",
computer_id, disk_part);
if (!result_update) {
dbi_conn_error(dbi->conn, &msglog);
syslog(LOG_ERR, "failed to query database (%s:%d) %s\n",
__func__, __LINE__, msglog);
return false;
}
dbi_result_free(result_update);
return true;
}
static int og_resp_refresh(json_t *data, struct og_client *cli)
{
struct og_partition partitions[OG_PARTITION_MAX] = {};
struct og_partition disks[OG_DISK_MAX] = {};
uint32_t num_disks = 0, num_partitions = 0;
struct og_cache_data cache_data = {};
const char *serial_number = NULL;
struct og_computer computer = {};
const char *status = NULL;
LIST_HEAD(boot_entry_list);
json_t *value = NULL;
struct og_dbi *dbi;
uint32_t link = 0;
const char *key;
int err = 0;
bool res;
og_cache_data_init(&cache_data);
if (json_typeof(data) != JSON_OBJECT)
goto err_out;
json_object_foreach(data, key, value) {
if (!strcmp(key, "disk_setup")) {
err = og_json_parse_partition_array(value, disks,
&num_disks);
} else if (!strcmp(key, "partition_setup")) {
err = og_json_parse_partition_array(value, partitions,
&num_partitions);
} else if (!strcmp(key, "serial_number")) {
err = og_json_parse_string(value, &serial_number);
} else if (!strcmp(key, "status")) {
err = og_json_parse_string(value, &status);
} else if (!strcmp(key, "link")) {
err = og_json_parse_uint(value, &link);
} else if (!strcmp(key, "efi")) {
err = og_json_parse_efi(value, &boot_entry_list);
} else if (!strcmp(key, "cache")) {
err = og_json_parse_cache(value, &cache_data);
}
if (err < 0)
goto err_out;
}
if (link)
cli->speed = link;
/*
* status is the only received field when the response is coming from a
* client using linux/windows mode.
*/
if (status) {
if (!strncmp(status, "LINUX", strlen("LINUX"))) {
cli->status = OG_CLIENT_STATUS_LINUX;
} else if (!strncmp(status, "WIN", strlen("WIN"))) {
cli->status = OG_CLIENT_STATUS_WIN;
}
return 0;
}
dbi = og_dbi_open(&ogconfig.db);
if (!dbi) {
syslog(LOG_ERR, "cannot open connection database (%s:%d)\n",
__func__, __LINE__);
goto err_out;
}
err = og_dbi_get_computer_info(dbi, &computer, cli->addr.sin_addr);
if (err < 0) {
og_dbi_close(dbi);
goto err_out;
}
if (og_update_cache_info(dbi, &cache_data, computer.id) < 0) {
og_dbi_close(dbi);
goto err_out;
}
res = og_update_boot_entries(dbi, &boot_entry_list, computer.id);
if (err < 0) {
og_dbi_close(dbi);
goto err_out;
}
res = og_update_client_disk_info(dbi, cli, computer.id, disks, num_disks,
partitions, num_partitions,
serial_number);
og_dbi_close(dbi);
if (!res) {
syslog(LOG_ERR, "Problem updating client configuration\n");
goto err_out;
}
og_cache_data_free(&cache_data);
og_boot_entry_free(&boot_entry_list);
return 0;
err_out:
syslog(LOG_ERR, "Failed to refresh info from client %s\n",
inet_ntoa(cli->addr.sin_addr));
og_cache_data_free(&cache_data);
og_boot_entry_free(&boot_entry_list);
return -1;
}
static bool og_dbi_update_image(struct og_dbi *dbi,
const struct og_image_legacy *img_info,
const char *computer_id)
{
int repo_id, sw_id, repo_alias;
const char *msglog;
dbi_result result;
uint32_t revision;
/* find repository identifier by repository ip and computer ID. */
result = dbi_conn_queryf(dbi->conn,
"SELECT repositorios.idrepositorio, repositorios.alias"
" FROM repositorios"
" LEFT JOIN ordenadores USING (idrepositorio)"
" WHERE repositorios.ip='%s' AND ordenadores.idordenador=%s",
img_info->repo, computer_id);
if (!result) {
dbi_conn_error(dbi->conn, &msglog);
syslog(LOG_ERR, "failed to query database (%s:%d) %s\n",
__func__, __LINE__, msglog);
return false;
}
if (!dbi_result_next_row(result)) {
syslog(LOG_ERR,
"repository does not exist in database (%s:%d)\n",
__func__, __LINE__);
dbi_result_free(result);
return false;
}
repo_alias = dbi_result_get_uint(result, "alias");
if (repo_alias)
repo_id = repo_alias;
else
repo_id = dbi_result_get_uint(result, "idrepositorio");
dbi_result_free(result);
/* find software id by computer ID, disk number and partition. */
result = dbi_conn_queryf(dbi->conn,
"SELECT idperfilsoft"
" FROM ordenadores_particiones"
" WHERE idordenador=%s AND numdisk=%s AND numpar=%s",
computer_id, img_info->disk, img_info->part);
if (!result) {
dbi_conn_error(dbi->conn, &msglog);
syslog(LOG_ERR, "failed to query database (%s:%d) %s\n",
__func__, __LINE__, msglog);
return false;
}
if (!dbi_result_next_row(result)) {
syslog(LOG_ERR,
"software profile does not exist in database (%s:%d)\n",
__func__, __LINE__);
dbi_result_free(result);
return false;
}
sw_id = dbi_result_get_uint(result, "idperfilsoft");
dbi_result_free(result);
/* update image table with this new image. */
result = dbi_conn_queryf(dbi->conn,
"UPDATE imagenes"
" SET idordenador=%s, numdisk=%s, numpar=%s, codpar=%s,"
" idperfilsoft=%d, idrepositorio=%d,"
" fechacreacion=NOW(), revision=revision+1"
" WHERE idimagen=%s",
computer_id, img_info->disk, img_info->part, img_info->code,
sw_id, repo_id, img_info->image_id);
if (!result) {
dbi_conn_error(dbi->conn, &msglog);
syslog(LOG_ERR, "failed to query database (%s:%d) %s\n",
__func__, __LINE__, msglog);
return false;
}
dbi_result_free(result);
result = dbi_conn_queryf(dbi->conn,
"SELECT revision FROM imagenes WHERE idimagen=%s",
img_info->image_id);
if (!result) {
dbi_conn_error(dbi->conn, &msglog);
syslog(LOG_ERR, "failed to query database (%s:%d) %s\n",
__func__, __LINE__, msglog);
return false;
}
if (!dbi_result_next_row(result)) {
syslog(LOG_ERR,
"no image found with id '%s' database (%s:%d)\n",
img_info->image_id, __func__, __LINE__);
dbi_result_free(result);
return false;
}
revision = dbi_result_get_uint(result, "revision");
dbi_result_free(result);
/* attach image to partition. */
result = dbi_conn_queryf(dbi->conn,
"UPDATE ordenadores_particiones"
" SET idimagen=%s, revision=%u, fechadespliegue=NOW()"
" WHERE idordenador=%s AND numdisk=%s AND numpar=%s",
img_info->image_id, revision,
computer_id, img_info->disk, img_info->part);
if (!result) {
dbi_conn_error(dbi->conn, &msglog);
syslog(LOG_ERR, "failed to query database (%s:%d) %s\n",
__func__, __LINE__, msglog);
return false;
}
dbi_result_free(result);
return true;
}
static int update_image_info(struct og_dbi *dbi, const char *image_id,
const char *clonator, const char *compressor,
const char *filesystem, const uint64_t datasize,
uint64_t size, uint64_t lastupdate, uint32_t perms,
const char *checksum)
{
const char *msglog;
dbi_result result;
result = dbi_conn_queryf(dbi->conn,
"UPDATE imagenes"
" SET clonator='%s', compressor='%s',"
" filesystem='%s', datasize=%lld, "
" size=%lld, lastupdate=%lld, permissions=%u, checksum='%s' "
" WHERE idimagen=%s", clonator, compressor, filesystem,
datasize, size, lastupdate, perms, checksum, image_id);
if (!result) {
dbi_conn_error(dbi->conn, &msglog);
syslog(LOG_ERR, "failed to query database (%s:%d) %s\n",
__func__, __LINE__, msglog);
return -1;
}
dbi_result_free(result);
return 0;
}
static int og_resp_image_create(json_t *data, struct og_client *cli)
{
uint64_t datasize = 0, size = 0, lastupdate = 0;
struct og_software_legacy soft_legacy;
struct og_image_legacy img_legacy;
struct og_computer computer = {};
const char *compressor = NULL;
const char *filesystem = NULL;
const char *partition = NULL;
const char *checksum = NULL;
const char *software = NULL;
const char *image_id = NULL;
const char *clonator = NULL;
const char *disk = NULL;
const char *code = NULL;
const char *name = NULL;
const char *repo = NULL;
uint32_t perms = 0;
struct og_dbi *dbi;
const char *key;
json_t *value;
int err = 0;
bool res;
if (json_typeof(data) != JSON_OBJECT)
return -1;
json_object_foreach(data, key, value) {
if (!strcmp(key, "software"))
err = og_json_parse_string(value, &software);
else if (!strcmp(key, "partition"))
err = og_json_parse_string(value, &partition);
else if (!strcmp(key, "disk"))
err = og_json_parse_string(value, &disk);
else if (!strcmp(key, "code"))
err = og_json_parse_string(value, &code);
else if (!strcmp(key, "id"))
err = og_json_parse_string(value, &image_id);
else if (!strcmp(key, "name"))
err = og_json_parse_string(value, &name);
else if (!strcmp(key, "repository"))
err = og_json_parse_string(value, &repo);
else if (!strcmp(key, "clonator"))
err = og_json_parse_string(value, &clonator);
else if (!strcmp(key, "compressor"))
err = og_json_parse_string(value, &compressor);
else if (!strcmp(key, "filesystem"))
err = og_json_parse_string(value, &filesystem);
else if (!strcmp(key, "datasize"))
err = og_json_parse_uint64(value, &datasize);
else if (!strcmp(key, "size"))
err = og_json_parse_uint64(value, &size);
else if (!strcmp(key, "lastupdate"))
err = og_json_parse_uint64(value, &lastupdate);
else if (!strcmp(key, "perms"))
err = og_json_parse_uint(value, &perms);
else if (!strcmp(key, "checksum"))
err = og_json_parse_string(value, &checksum);
if (err < 0)
return err;
}
if (!software || !partition || !disk || !code || !image_id || !name ||
!repo || !clonator || !compressor || !filesystem || !datasize) {
syslog(LOG_ERR, "malformed response json\n");
return -1;
}
dbi = og_dbi_open(&ogconfig.db);
if (!dbi) {
syslog(LOG_ERR, "cannot open connection database (%s:%d)\n",
__func__, __LINE__);
return -1;
}
err = og_dbi_get_computer_info(dbi, &computer, cli->addr.sin_addr);
if (err < 0) {
og_dbi_close(dbi);
return -1;
}
snprintf(soft_legacy.center, sizeof(soft_legacy.center), "%d",
computer.center);
snprintf(soft_legacy.software, sizeof(soft_legacy.software), "%s",
software);
snprintf(img_legacy.image_id, sizeof(img_legacy.image_id), "%s",
image_id);
snprintf(soft_legacy.id, sizeof(soft_legacy.id), "%d", computer.id);
snprintf(img_legacy.part, sizeof(img_legacy.part), "%s", partition);
snprintf(img_legacy.disk, sizeof(img_legacy.disk), "%s", disk);
snprintf(img_legacy.code, sizeof(img_legacy.code), "%s", code);
snprintf(img_legacy.name, sizeof(img_legacy.name), "%s", name);
snprintf(img_legacy.repo, sizeof(img_legacy.repo), "%s", repo);
res = actualizaSoftware(dbi,
soft_legacy.software,
img_legacy.part,
soft_legacy.id,
computer.name,
soft_legacy.center);
if (!res) {
og_dbi_close(dbi);
syslog(LOG_ERR, "Problem updating client configuration\n");
return -1;
}
res = og_dbi_update_image(dbi, &img_legacy, soft_legacy.id);
if (!res) {
og_dbi_close(dbi);
syslog(LOG_ERR, "Problem updating client configuration\n");
return -1;
}
res = update_image_info(dbi, image_id, clonator, compressor,
filesystem, datasize, size, lastupdate, perms,
checksum);
og_dbi_close(dbi);
if (res) {
syslog(LOG_ERR, "Problem updating image info\n");
return -1;
}
return 0;
}
static int og_resp_image_restore(json_t *data, struct og_client *cli)
{
struct og_software_legacy soft_legacy;
struct og_image_legacy img_legacy;
struct og_computer computer = {};
const char *partition = NULL;
const char *image_id = NULL;
const char *disk = NULL;
const char *msglog;
struct og_dbi *dbi;
dbi_result result;
const char *key;
json_t *value;
int err = 0;
if (json_typeof(data) != JSON_OBJECT)
return -1;
json_object_foreach(data, key, value) {
if (!strcmp(key, "partition"))
err = og_json_parse_string(value, &partition);
else if (!strcmp(key, "disk"))
err = og_json_parse_string(value, &disk);
else if (!strcmp(key, "image_id"))
err = og_json_parse_string(value, &image_id);
if (err < 0)
return err;
}
if (!partition || !disk || !image_id) {
syslog(LOG_ERR, "malformed response json\n");
return -1;
}
dbi = og_dbi_open(&ogconfig.db);
if (!dbi) {
syslog(LOG_ERR, "cannot open connection database (%s:%d)\n",
__func__, __LINE__);
return -1;
}
result = dbi_conn_queryf(dbi->conn,
"SELECT idperfilsoft FROM imagenes "
" WHERE idimagen='%s'", image_id);
if (!result) {
og_dbi_close(dbi);
syslog(LOG_ERR, "failed to query database\n");
return -1;
}
if (!dbi_result_next_row(result)) {
dbi_result_free(result);
og_dbi_close(dbi);
syslog(LOG_ERR, "software profile does not exist in database\n");
return -1;
}
snprintf(img_legacy.software_id, sizeof(img_legacy.software_id),
"%d", dbi_result_get_uint(result, "idperfilsoft"));
dbi_result_free(result);
err = og_dbi_get_computer_info(dbi, &computer, cli->addr.sin_addr);
if (err < 0) {
og_dbi_close(dbi);
return -1;
}
snprintf(img_legacy.image_id, sizeof(img_legacy.image_id), "%s",
image_id);
snprintf(img_legacy.part, sizeof(img_legacy.part), "%s", partition);
snprintf(img_legacy.disk, sizeof(img_legacy.disk), "%s", disk);
snprintf(soft_legacy.id, sizeof(soft_legacy.id), "%d", computer.id);
result = dbi_conn_queryf(dbi->conn,
"UPDATE ordenadores_particiones"
" SET idimagen=%s, idperfilsoft=%s, fechadespliegue=NOW(),"
" revision=(SELECT revision FROM imagenes WHERE idimagen=%s),"
" idnombreso=IFNULL((SELECT idnombreso FROM perfilessoft WHERE idperfilsoft=%s),0)"
" WHERE idordenador=%s AND numdisk=%s AND numpar=%s",
img_legacy.image_id, img_legacy.software_id,
img_legacy.image_id, img_legacy.software_id,
soft_legacy.id, img_legacy.disk, img_legacy.part);
if (!result) {
dbi_conn_error(dbi->conn, &msglog);
syslog(LOG_ERR, "failed to update database (%s:%d) %s\n",
__func__, __LINE__, msglog);
og_dbi_close(dbi);
return -1;
}
dbi_result_free(result);
og_dbi_close(dbi);
return 0;
}
static int og_agent_http_response_code(const char *buf)
{
if (!strncmp(buf, "HTTP/1.0 200 OK", strlen("HTTP/1.0 200 OK"))) {
return 200;
} else if (!strncmp(buf, "HTTP/1.0 202 Accepted",
strlen("HTTP/1.0 202 Accepted"))) {
return 202;
} else if (!strncmp(buf, "HTTP/1.0 400 Bad Request",
strlen("HTTP/1.0 400 Bad Request"))) {
return 400;
} else if (!strncmp(buf, "HTTP/1.0 500 Internal Server Error",
strlen("HTTP/1.0 500 Internal Server Error"))) {
return 500;
} else if (!strncmp(buf, "HTTP/1.0 503 Service Unavailable",
strlen("HTTP/1.0 503 Service Unavailable"))) {
return 503;
} else if (!strncmp(buf, "HTTP/1.0 103 Early Hints",
strlen("HTTP/1.0 103 Early Hints"))) {
return 103;
}
return -1;
}
int og_agent_state_process_response(struct og_client *cli)
{
int ret, err = -1, code;
json_error_t json_err;
bool success;
json_t *root;
char *body;
code = og_agent_http_response_code(cli->buf);
switch (code) {
case 103:
case 200:
ret = 0;
success = true;
break;
case 202:
ret = 1;
success = true;
break;
case 400:
ret = -1;
success = false;
syslog(LOG_ERR, "Client %s:%hu reports malformed HTTP request from server\n",
inet_ntoa(cli->addr.sin_addr), ntohs(cli->addr.sin_port));
break;
case 500:
ret = 0;
success = false;
cli->last_cmd = OG_CMD_UNSPEC;
syslog(LOG_ERR, "Client %s:%hu reports failure to process command\n",
inet_ntoa(cli->addr.sin_addr), ntohs(cli->addr.sin_port));
/* ... cancel pending actions related to this task for this client here */
break;
case 503:
ret = 1;
success = false;
syslog(LOG_ERR, "Client %s:%hu is busy to process command\n",
inet_ntoa(cli->addr.sin_addr), ntohs(cli->addr.sin_port));
break;
default:
ret = -1;
success = false;
syslog(LOG_ERR, "Client %s:%hu reports unknown HTTP response code\n",
inet_ntoa(cli->addr.sin_addr), ntohs(cli->addr.sin_port));
break;
}
if (success)
cli->last_cmd_result = OG_SUCCESS;
else
cli->last_cmd_result = OG_FAILURE;
if (code != 200 && code != 103) {
cli->last_cmd_id = 0;
return ret;
}
if (!cli->content_length) {
cli->last_cmd_id = 0;
cli->last_cmd = OG_CMD_UNSPEC;
return 0;
}
body = strstr(cli->buf, "\r\n\r\n") + 4;
root = json_loads(body, 0, &json_err);
if (!root) {
syslog(LOG_ERR, "%s:%d: malformed json line %d: %s\n",
__FILE__, __LINE__, json_err.line, json_err.text);
return -1;
}
if (code == 103) {
err = og_resp_early_hints(cli, root);
json_decref(root);
return err;
}
switch (cli->last_cmd) {
case OG_CMD_SHELL_RUN:
err = og_resp_shell_run(cli, root);
break;
case OG_CMD_HARDWARE:
err = og_resp_hardware(root, cli);
break;
case OG_CMD_SOFTWARE:
err = og_resp_software(root, cli);
break;
case OG_CMD_REFRESH:
err = og_resp_refresh(root, cli);
break;
case OG_CMD_SETUP:
err = og_resp_refresh(root, cli);
break;
case OG_CMD_IMAGE_CREATE:
err = og_resp_image_create(root, cli);
break;
case OG_CMD_IMAGE_RESTORE:
err = og_resp_image_restore(root, cli);
if (!err)
err = og_resp_update_cache(root, cli);
break;
case OG_CMD_CACHE_DELETE:
err = og_resp_update_cache(root, cli);
break;
case OG_CMD_CACHE_FETCH:
err = og_resp_update_cache(root, cli);
break;
default:
err = -1;
break;
}
json_decref(root);
if (err < 0) {
err = 0;
success = false;
cli->last_cmd_result = OG_FAILURE;
/* ... cancel pending actions related to this task for this client here */
}
cli->last_cmd_id = 0;
cli->last_cmd = OG_CMD_UNSPEC;
return err;
}