master
c 241 lines 7.54 KB
Raw
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 }