/* * Copyright (C) 2020-2021 Soleta Networks * * 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 #include #include #include #include #include #include #include #include #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; uint64_t part_code; 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; } if (safe_strtoull(disks[i].size, &reported_disk.size, 10, UINT64_MAX) < 0) { syslog(LOG_ERR, "failed to parse disk size for disk %d (%s:%d)\n", i + 1, __func__, __LINE__); return false; } 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; } if (safe_strtoull(partitions[i].size, &reported_part.size, 10, UINT64_MAX) < 0) { syslog(LOG_ERR, "failed to parse partition size %s for partition %d (%s:%d)\n", partitions[i].size, i + 1, __func__, __LINE__); return false; } if (safe_strtoull(partitions[i].code, &part_code, 32, UINT32_MAX) < 0) { syslog(LOG_ERR, "failed to parse partition code %s for partition %d (%s:%d)\n", partitions[i].code, i + 1, __func__, __LINE__); return false; } reported_part.code = part_code; 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; } static int og_resp_image_create_error(struct og_client *cli) { struct og_dbi *dbi; dbi = og_dbi_open(&ogconfig.db); if (!dbi) { syslog(LOG_ERR, "cannot open connection database (%s:%d)\n", __func__, __LINE__); return -1; } if (og_dbi_delete_image(dbi, cli->last_cmd.ctx.image.id) < 0) { syslog(LOG_WARNING, "Cannot delete image stub with id %d\n", cli->last_cmd.ctx.image.id); return -1; } og_dbi_close(dbi); return 0; } static void og_client_reset_cmd(struct og_client *cli) { cli->last_cmd.id = 0; cli->last_cmd.type = OG_CMD_UNSPEC; memset(&cli->last_cmd.ctx, 0, sizeof(cli->last_cmd.ctx)); } int og_agent_state_process_response(struct og_client *cli) { enum og_cmd_type cmd_type = cli->last_cmd.type; 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.type = 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 (!success && cmd_type == OG_CMD_IMAGE_CREATE) { syslog(LOG_ERR, "Client %s:%hu reports failure when creating image with id %d\n", inet_ntoa(cli->addr.sin_addr), ntohs(cli->addr.sin_port), cli->last_cmd.ctx.image.id); og_resp_image_create_error(cli); } if (code != 200 && code != 103) { cli->last_cmd.id = 0; memset(&cli->last_cmd.ctx, 0, sizeof(cli->last_cmd.ctx)); return ret; } if (!cli->content_length) { og_client_reset_cmd(cli); 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.type) { 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); if (err < 0) og_resp_image_create_error(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 */ } og_client_reset_cmd(cli); return err; }