| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | #include "api_v2_calls.h" |
| 4 | #include "claim/claim.h" |
| 5 | |
| 6 | static char *netdata_random_session_id_filename = NULL; |
| 7 | static nd_uuid_t netdata_random_session_id = { 0 }; |
| 8 | |
| 9 | bool netdata_random_session_id_generate(void) { |
| 10 | static char guid[UUID_STR_LEN] = ""; |
| 11 | |
| 12 | uuid_generate_random(netdata_random_session_id); |
| 13 | uuid_unparse_lower(netdata_random_session_id, guid); |
| 14 | |
| 15 | char filename[FILENAME_MAX + 1]; |
| 16 | snprintfz(filename, FILENAME_MAX, "%s/netdata_random_session_id", netdata_configured_varlib_dir); |
| 17 | |
| 18 | bool ret = true; |
| 19 | |
| 20 | (void)unlink(filename); |
| 21 | |
| 22 | // save it |
| 23 | int fd = open(filename, O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC, 640); |
| 24 | if(fd == -1) { |
| 25 | netdata_log_error("Cannot create random session id file '%s'.", filename); |
| 26 | ret = false; |
| 27 | } |
| 28 | else { |
| 29 | if (write(fd, guid, UUID_STR_LEN - 1) != UUID_STR_LEN - 1) { |
| 30 | netdata_log_error("Cannot write the random session id file '%s'.", filename); |
| 31 | ret = false; |
| 32 | } else { |
| 33 | ssize_t bytes = write(fd, "\n", 1); |
| 34 | UNUSED(bytes); |
| 35 | } |
| 36 | close(fd); |
| 37 | } |
| 38 | |
| 39 | if(ret && (!netdata_random_session_id_filename || strcmp(netdata_random_session_id_filename, filename) != 0)) { |
| 40 | freez(netdata_random_session_id_filename); |
| 41 | netdata_random_session_id_filename = strdupz(filename); |
| 42 | } |
| 43 | |
| 44 | return ret; |
| 45 | } |
| 46 | |
| 47 | static const char *netdata_random_session_id_get_filename(void) { |
| 48 | if(!netdata_random_session_id_filename) |
| 49 | netdata_random_session_id_generate(); |
| 50 | |
| 51 | return netdata_random_session_id_filename; |
| 52 | } |
| 53 | |
| 54 | static bool netdata_random_session_id_matches(const char *guid) { |
| 55 | if(uuid_is_null(netdata_random_session_id)) |
| 56 | return false; |
| 57 | |
| 58 | nd_uuid_t uuid; |
| 59 | |
| 60 | if(uuid_parse(guid, uuid)) |
| 61 | return false; |
| 62 | |
| 63 | if(uuid_compare(netdata_random_session_id, uuid) == 0) |
| 64 | return true; |
| 65 | |
| 66 | return false; |
| 67 | } |
| 68 | |
| 69 | static bool check_claim_param(const char *s) { |
| 70 | if(!s || !*s) return true; |
| 71 | |
| 72 | do { |
| 73 | if(isalnum((uint8_t)*s) || *s == '.' || *s == ',' || *s == '-' || *s == ':' || *s == '/' || *s == '_') |
| 74 | ; |
| 75 | else |
| 76 | return false; |
| 77 | |
| 78 | } while(*++s); |
| 79 | |
| 80 | return true; |
| 81 | } |
| 82 | |
| 83 | static bool agent_can_be_claimed(void) { |
| 84 | CLOUD_STATUS status = cloud_status(); |
| 85 | switch(status) { |
| 86 | case CLOUD_STATUS_AVAILABLE: |
| 87 | case CLOUD_STATUS_OFFLINE: |
| 88 | case CLOUD_STATUS_INDIRECT: |
| 89 | return true; |
| 90 | |
| 91 | case CLOUD_STATUS_BANNED: |
| 92 | case CLOUD_STATUS_ONLINE: |
| 93 | return false; |
| 94 | } |
| 95 | |
| 96 | return false; |
| 97 | } |
| 98 | |
| 99 | typedef enum { |
| 100 | CLAIM_RESP_INFO, |
| 101 | CLAIM_RESP_ERROR, |
| 102 | CLAIM_RESP_ACTION_OK, |
| 103 | CLAIM_RESP_ACTION_FAILED, |
| 104 | } CLAIM_RESPONSE; |
| 105 | |
| 106 | static void claim_add_user_info_command(BUFFER *wb) { |
| 107 | const char *filename = netdata_random_session_id_get_filename(); |
| 108 | CLEAN_BUFFER *os_cmd = buffer_create(0, NULL); |
| 109 | |
| 110 | const char *os_filename; |
| 111 | const char *os_prefix; |
| 112 | const char *os_quote; |
| 113 | const char *os_message; |
| 114 | |
| 115 | #if defined(OS_WINDOWS) |
| 116 | char win_path[FILENAME_MAX]; |
| 117 | if(cygwin_conv_path(CCP_POSIX_TO_WIN_A, filename, win_path, sizeof(win_path)) == 0) |
| 118 | os_filename = win_path; |
| 119 | else |
| 120 | os_filename = os_translate_path(win_path, filename, sizeof(win_path)); |
| 121 | os_prefix = "more"; |
| 122 | os_message = "We need to verify this Windows server is yours. So, open a Command Prompt on this server to run the command. It will give you a UUID. Copy and paste this UUID to this box:"; |
| 123 | #else |
| 124 | os_filename = filename; |
| 125 | if (localhost_is_docker()) |
| 126 | os_prefix = "docker exec netdata cat"; |
| 127 | else |
| 128 | os_prefix = "sudo cat"; |
| 129 | os_message = "We need to verify this server is yours. SSH to this server and run this command. It will give you a UUID. Copy and paste this UUID to this box:"; |
| 130 | #endif |
| 131 | |
| 132 | // add quotes only when the filename has a space |
| 133 | if(strchr(os_filename, ' ')) |
| 134 | os_quote = "\""; |
| 135 | else |
| 136 | os_quote = ""; |
| 137 | |
| 138 | buffer_sprintf(os_cmd, "%s %s%s%s", os_prefix, os_quote, os_filename, os_quote); |
| 139 | |
| 140 | buffer_json_member_add_string(wb, "key_filename", os_filename); |
| 141 | buffer_json_member_add_string(wb, "cmd", buffer_tostring(os_cmd)); |
| 142 | buffer_json_member_add_string(wb, "help", os_message); |
| 143 | } |
| 144 | |
| 145 | static int claim_json_response(BUFFER *wb, CLAIM_RESPONSE response, const char *msg) { |
| 146 | time_t now_s = now_realtime_sec(); |
| 147 | buffer_reset(wb); |
| 148 | buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_DEFAULT); |
| 149 | |
| 150 | if(response != CLAIM_RESP_INFO) { |
| 151 | // this is not an info, so it needs a status report |
| 152 | buffer_json_member_add_boolean(wb, "success", response == CLAIM_RESP_ACTION_OK ? true : false); |
| 153 | buffer_json_member_add_string_or_empty(wb, "message", msg ? msg : ""); |
| 154 | } |
| 155 | |
| 156 | buffer_json_cloud_status(wb, now_s); |
| 157 | |
| 158 | if(response != CLAIM_RESP_ACTION_OK) { |
| 159 | buffer_json_member_add_boolean(wb, "can_be_claimed", agent_can_be_claimed()); |
| 160 | claim_add_user_info_command(wb); |
| 161 | } |
| 162 | |
| 163 | buffer_json_agents_v2(wb, NULL, now_s, false, false, 0); |
| 164 | buffer_json_finalize(wb); |
| 165 | |
| 166 | return (response == CLAIM_RESP_ERROR) ? HTTP_RESP_BAD_REQUEST : HTTP_RESP_OK; |
| 167 | } |
| 168 | |
| 169 | static int claim_txt_response(BUFFER *wb, const char *msg) { |
| 170 | buffer_reset(wb); |
| 171 | buffer_strcat(wb, msg); |
| 172 | return HTTP_RESP_BAD_REQUEST; |
| 173 | } |
| 174 | |
| 175 | static int api_claim(uint8_t version, struct web_client *w, char *url) { |
| 176 | char *key = NULL; |
| 177 | char *token = NULL; |
| 178 | char *rooms = NULL; |
| 179 | char *base_url = NULL; |
| 180 | |
| 181 | while (url) { |
| 182 | char *value = strsep_skip_consecutive_separators(&url, "&"); |
| 183 | if (!value || !*value) continue; |
| 184 | |
| 185 | char *name = strsep_skip_consecutive_separators(&value, "="); |
| 186 | if (!name || !*name) continue; |
| 187 | if (!value || !*value) continue; |
| 188 | |
| 189 | if(!strcmp(name, "key")) |
| 190 | key = value; |
| 191 | else if(!strcmp(name, "token")) |
| 192 | token = value; |
| 193 | else if(!strcmp(name, "rooms")) |
| 194 | rooms = value; |
| 195 | else if(!strcmp(name, "url")) |
| 196 | base_url = value; |
| 197 | } |
| 198 | |
| 199 | BUFFER *wb = w->response.data; |
| 200 | |
| 201 | CLAIM_RESPONSE response = CLAIM_RESP_INFO; |
| 202 | const char *msg = NULL; |
| 203 | bool can_be_claimed = agent_can_be_claimed(); |
| 204 | |
| 205 | if(can_be_claimed && key) { |
| 206 | if(!netdata_random_session_id_matches(key)) { |
| 207 | netdata_random_session_id_generate(); // generate a new key, to avoid an attack to find it |
| 208 | if(version < 3) return claim_txt_response(wb, "invalid key"); |
| 209 | return claim_json_response(wb, CLAIM_RESP_ERROR, "invalid key"); |
| 210 | } |
| 211 | |
| 212 | if(!token || !base_url || !check_claim_param(token) || !check_claim_param(base_url) || (rooms && !check_claim_param(rooms))) { |
| 213 | netdata_random_session_id_generate(); // generate a new key, to avoid an attack to find it |
| 214 | if(version < 3) return claim_txt_response(wb, "invalid parameters"); |
| 215 | return claim_json_response(wb, CLAIM_RESP_ERROR, "invalid parameters"); |
| 216 | } |
| 217 | |
| 218 | netdata_random_session_id_generate(); // generate a new key, to avoid an attack to find it |
| 219 | |
| 220 | if(claim_agent(base_url, token, rooms, cloud_config_proxy_get(), cloud_config_insecure_get())) { |
| 221 | msg = "ok"; |
| 222 | can_be_claimed = false; |
| 223 | claim_reload_and_wait_online(); |
| 224 | response = CLAIM_RESP_ACTION_OK; |
| 225 | } |
| 226 | else { |
| 227 | msg = claim_agent_failure_reason_get(); |
| 228 | response = CLAIM_RESP_ACTION_FAILED; |
| 229 | } |
| 230 | } |
| 231 | |
| 232 | return claim_json_response(wb, response, msg); |
| 233 | } |
| 234 | |
| 235 | int api_v2_claim(RRDHOST *host __maybe_unused, struct web_client *w, char *url) { |
| 236 | return api_claim(2, w, url); |
| 237 | } |
| 238 | |
| 239 | int api_v3_claim(RRDHOST *host __maybe_unused, struct web_client *w, char *url) { |
| 240 | return api_claim(3, w, url); |
| 241 | } |