1 /* $Id: ncbi_namerd.c 612632 2020-07-24 17:49:15Z lavr $
2 * ===========================================================================
3 *
4 * PUBLIC DOMAIN NOTICE
5 * National Center for Biotechnology Information
6 *
7 * This software/database is a "United States Government Work" under the
8 * terms of the United States Copyright Act. It was written as part of
9 * the author's official duties as a United States Government employee and
10 * thus cannot be copyrighted. This software/database is freely available
11 * to the public for use. The National Library of Medicine and the U.S.
12 * Government have not placed any restriction on its use or reproduction.
13 *
14 * Although all reasonable efforts have been taken to ensure the accuracy
15 * and reliability of the software and data, the NLM and the U.S.
16 * Government do not and cannot warrant the performance or results that
17 * may be obtained by using this software or data. The NLM and the U.S.
18 * Government disclaim all warranties, express or implied, including
19 * warranties of performance, merchantability or fitness for any particular
20 * purpose.
21 *
22 * Please cite the author in any work or product based on this material.
23 *
24 * ===========================================================================
25 *
26 * Authors: Anton Lavrentiev, David McElhany
27 *
28 * File Description:
29 * Low-level API to resolve an NCBI service name to server meta-addresses
30 * with the use of NAMERD.
31 *
32 */
33
34 #include "ncbi_ansi_ext.h"
35 #include "ncbi_comm.h"
36 #include "ncbi_lb.h"
37 #include "ncbi_linkerd.h"
38 #include "ncbi_namerd.h"
39 #include "ncbi_once.h"
40 #include "parson.h"
41
42 #include <connect/ncbi_buffer.h>
43 #include <connect/ncbi_connutil.h>
44 #include <connect/ncbi_http_connector.h>
45 #include <connect/ncbi_memory_connector.h>
46 #include <connect/ncbi_server_info.h>
47
48 #include <ctype.h>
49 #include <stdlib.h>
50 #include <time.h>
51
52
53 #ifdef _MSC_VER
54 #define FMT_SIZE_T "%llu"
55 #define FMT_TIME_T "%llu"
56 #else
57 #define FMT_SIZE_T "%zu"
58 #define FMT_TIME_T "%lu"
59 #endif
60
61
62 #define NCBI_USE_ERRCODE_X Connect_Namerd
63
64
65 /* NAMERD subcodes for CORE_LOG*X() macros */
66 enum ENAMERD_Subcodes {
67 eNSub_Message = 0, /**< not an error */
68 eNSub_Alloc = 1, /**< memory allocation failed */
69 eNSub_BadData = 2, /**< bad data was provided */
70 eNSub_Connect = 3, /**< problem in connect library */
71 eNSub_HttpRead = 4, /**< failed reading from HTTP conn */
72 eNSub_Json = 5, /**< a JSON parsing failure */
73 eNSub_Libc = 6, /**< a standard library failure */
74 eNSub_NoService = 7, /**< couldn't reach namerd service provider */
75 eNSub_TooLong = 8, /**< data was too long to fit in a buffer */
76 eNSub_Logic = 9 /**< logic error */
77 };
78
79
80 /* Apache is limited to around 4000 byte query strings. -- todo: find reference
81 * Note that this is approximate and doesn't need to be precise because
82 * it's more than should be required for namerd anyway.
83 */
84 #define MAX_QRY_STR_LEN 4000
85
86 /* This is hard-coded in the definition of SConnNetInfo in ncbi_connutil.h */
87 #define MAX_ARGS_LEN 2048
88
89 /* Misc. */
90 #define DTAB_HDR_FIELD_NAME "DTab-Local"
91 #define NIL '\0'
92
93
94 /* Registry entry names and default values for NAMERD "SConnNetInfo" fields.
95 Note that these are purely for the NAMERD API; they don't relate to any
96 other part of the connect library, returned endpoints, or client code.
97 Therefore, they are independent of other connect library macros.
98 Also, the namerd API doesn't support using a port so there's no macro for
99 that.
100 */
101 #define REG_NAMERD_SECTION "_NAMERD"
102
103 #define REG_NAMERD_PROXY_HOST_KEY "PROXY_HOST"
104
105 /* NAMERD_TODO - "temporarily" support plain "linkerd" on Unix only */
106 #if defined(NCBI_OS_UNIX) && ! defined(NCBI_OS_CYGWIN)
107 #define REG_NAMERD_PROXY_HOST_DEF "linkerd"
108 #else
109 #define REG_NAMERD_PROXY_HOST_DEF \
110 "pool.linkerd-proxy.service.bethesda-dev.consul.ncbi.nlm.nih.gov"
111 #endif
112
113 #define REG_NAMERD_PROXY_PORT_KEY "PROXY_PORT"
114 #define REG_NAMERD_PROXY_PORT_DEF "4140"
115
116 #define REG_NAMERD_API_HOST_KEY "API_HOST"
117 #define REG_NAMERD_API_HOST_DEF "namerd-api.linkerd.ncbi.nlm.nih.gov"
118
119 #define REG_NAMERD_API_PATH_KEY "API_PATH"
120 #define REG_NAMERD_API_PATH_DEF "/api/1/resolve"
121
122 #define REG_NAMERD_API_ARGS_KEY "API_ARGS"
123 #define REG_NAMERD_API_ARGS_DEF "path=/service/"
124
125 #define REG_NAMERD_API_REQ_KEY "API_REQ_METHOD"
126 #define REG_NAMERD_API_REQ_DEF "GET"
127
128 #define REG_NAMERD_API_SCHEME_KEY "API_SCHEME"
129 #define REG_NAMERD_API_SCHEME_DEF "http"
130
131 #define REG_NAMERD_API_ENV_KEY "API_ENVIRONMENT"
132 #define REG_NAMERD_API_ENV_DEF "default"
133
134 #define REG_NAMERD_DTAB_KEY "DTAB"
135 #define REG_NAMERD_DTAB_DEF ""
136
137 /* Default rate increase 20% if svc runs locally */
138 #define NAMERD_LOCAL_BONUS 1.2
139
140
141 #ifdef __cplusplus
142 extern "C" {
143 #endif /*__cplusplus*/
144
145 static int/*bool*/ s_Adjust(SConnNetInfo* net_info,
146 void* iter,
147 unsigned int unused);
148
149 static SSERV_Info* s_GetNextInfo(SERV_ITER, HOST_INFO*);
150 static void s_Reset (SERV_ITER);
151 static void s_Close (SERV_ITER);
152
153 static const SSERV_VTable s_op = {
154 s_GetNextInfo, NULL/*Feedback*/, NULL/*s_Update*/, s_Reset, s_Close, "NAMERD"
155 };
156
157 static EHTTP_HeaderParse s_ParseHeader(const char* header,
158 void* iter,
159 int server_error);
160
161 #ifdef __cplusplus
162 } /* extern "C" */
163 #endif /*__cplusplus*/
164
165
166 struct SNAMERD_Data {
167 short/*bool*/ eof; /* no more resolves */
168 short/*bool*/ fail; /* no more connects */
169 short/*bool*/ done; /* all endpoints have been processed */
170 SConnNetInfo* net_info;
171 SLB_Candidate* cand;
172 size_t n_cand;
173 size_t a_cand;
174 };
175
176
177 /* Some static variables needed only to support testing with mock data.
178 Testing with mock data is currently limited to single-threaded tests. */
179 static int s_initialized = 0;
180 static BUF s_mock_buf = NULL;
181 static const char* s_mock_body = NULL;
182
183
184 /* Set up the ability to flexibly use either a file or http connector for
185 reading from. This will enable higher performance normal operation while
186 also allowing file input for testing with minimal code impact.
187 */
188 static CONNECTOR s_CreateConnectorHttp(SERV_ITER iter);
189 static CONNECTOR s_CreateConnectorMemory(SERV_ITER iter);
190
191 typedef CONNECTOR (*fCreateConnector)(SERV_ITER iter);
192 static fCreateConnector s_CreateConnector = s_CreateConnectorHttp;
193
194
s_CreateConnectorHttp(SERV_ITER iter)195 static CONNECTOR s_CreateConnectorHttp(SERV_ITER iter)
196 {
197 CORE_TRACE("s_CreateConnectorHttp()");
198 struct SNAMERD_Data* data = NULL;
199
200 if ( ! iter) {
201 CORE_LOG_X(eNSub_Logic, eLOG_Critical,
202 "Unexpected NULL 'iter' pointer.");
203 return NULL;
204 }
205 if ( ! iter->data) {
206 CORE_LOG_X(eNSub_Logic, eLOG_Critical,
207 "Unexpected NULL 'iter->data' pointer.");
208 return NULL;
209 }
210 data = (struct SNAMERD_Data*) iter->data;
211
212 return HTTP_CreateConnectorEx(data->net_info, fHTTP_Flushable,
213 s_ParseHeader, iter/*data*/, s_Adjust, 0/*cleanup*/);
214 }
215
216
s_DestroyMockBuf(void)217 static void s_DestroyMockBuf(void)
218 {
219 if (s_mock_buf)
220 BUF_Destroy(s_mock_buf);
221 s_mock_buf = NULL;
222 }
223
224
s_CreateConnectorMemory(SERV_ITER iter)225 static CONNECTOR s_CreateConnectorMemory(SERV_ITER iter)
226 {
227 CORE_TRACE("s_CreateConnectorMemory()");
228 if ( ! s_mock_body) {
229 CORE_LOG_X(eNSub_Logic, eLOG_Critical,
230 "Unexpected NULL 's_mock_body' pointer.");
231 return NULL;
232 }
233
234 /* Reset buffer for each connector */
235 s_DestroyMockBuf();
236
237 BUF_Append(&s_mock_buf, (void*)s_mock_body, strlen(s_mock_body));
238
239 return MEMORY_CreateConnectorEx(s_mock_buf, 0);
240 }
241
242
s_Quit(void)243 static void s_Quit(void)
244 {
245 s_DestroyMockBuf();
246 CORE_LOCK_WRITE;
247 s_initialized = 0;
248 CORE_UNLOCK;
249 }
250
251
s_Init(void)252 static void s_Init(void)
253 {
254 int error;
255 CORE_LOCK_READ;
256 if (s_initialized) {
257 CORE_UNLOCK;
258 return;
259 }
260 CORE_UNLOCK;
261
262 error = 0;
263 CORE_LOCK_WRITE;
264 if (!s_initialized) {
265 if (atexit(s_Quit) != 0)
266 error = 1;
267 s_initialized = 1;
268 }
269 CORE_UNLOCK;
270 if (error) {
271 static void* s_Once = 0;
272 if (CORE_Once(&s_Once)) {
273 CORE_LOG_X(eNSub_Libc, eLOG_Error,
274 "Error registering atexit function.");
275 }
276 }
277 }
278
279
s_CONN_Create(SERV_ITER iter,CONNECTOR * c_p,CONN * conn_p)280 static EIO_Status s_CONN_Create(SERV_ITER iter, CONNECTOR* c_p, CONN* conn_p)
281 {
282 EIO_Status status = eIO_Unknown;
283
284 CORE_TRACE("Entering s_CONN_Create()");
285
286 /* require valid, NULL pointers */
287 if ( ! c_p) {
288 CORE_LOG_X(eNSub_Logic, eLOG_Critical,
289 "Unexpected NULL connector pointer pointer.");
290 return eIO_Unknown;
291 }
292 if (*c_p) {
293 CORE_LOG_X(eNSub_Logic, eLOG_Critical,
294 "Unexpected non-NULL connector pointer.");
295 return eIO_Unknown;
296 }
297 if ( ! conn_p) {
298 CORE_LOG_X(eNSub_Logic, eLOG_Critical,
299 "Unexpected NULL connection pointer pointer.");
300 return eIO_Unknown;
301 }
302 if (*conn_p) {
303 CORE_LOG_X(eNSub_Logic, eLOG_Critical,
304 "Unexpected non-NULL connection pointer.");
305 return eIO_Unknown;
306 }
307
308 *c_p = s_CreateConnector(iter);
309 if (*c_p) {
310 status = CONN_Create(*c_p, conn_p);
311 if (status != eIO_Success) {
312 CORE_LOGF_X(eNSub_Connect, eLOG_Error,
313 ("Unable to create connection, status = %s.",
314 IO_StatusStr(status)));
315 if ((*c_p)->destroy && (*c_p)->handle)
316 (*c_p)->destroy(*c_p);
317 *c_p = NULL;
318 *conn_p = NULL;
319 }
320 } else {
321 CORE_LOG_X(eNSub_Connect, eLOG_Error, "Unable to create connector.");
322 }
323
324 CORE_TRACE("Leaving s_CONN_Create()");
325 return status;
326 }
327
328
s_CONN_Destroy(CONNECTOR * c_p,CONN * conn_p)329 static void s_CONN_Destroy(CONNECTOR* c_p, CONN* conn_p)
330 {
331 if ( ! c_p) {
332 CORE_LOG_X(eNSub_Logic, eLOG_Critical,
333 "Unexpected NULL connector pointer.");
334 } else {
335 *c_p = NULL;
336 }
337 if ( ! conn_p) {
338 CORE_LOG_X(eNSub_Logic, eLOG_Critical,
339 "Unexpected NULL connection pointer.");
340 } else {
341 if (*conn_p)
342 CONN_Close(*conn_p);
343 *conn_p = NULL;
344 }
345 s_DestroyMockBuf();
346 }
347
348
349 /* Update a dtab value by appending another entry. */
s_UpdateDtab(char ** dest_dtab_p,char * src_dtab,int * success_p)350 static void s_UpdateDtab(char** dest_dtab_p, char* src_dtab, int* success_p)
351 {
352 char* new_dtab = NULL;
353 char enc_dtab[MAX_QRY_STR_LEN + 1];
354 size_t new_size, src_size, enc_size;
355
356 CORE_TRACEF(("Entering s_UpdateDtab(\"%s\") -- old dtab = \"%s\"", src_dtab,
357 *dest_dtab_p ? *dest_dtab_p : ""));
358
359 if ( ! *success_p) {
360 CORE_TRACE("Leaving s_UpdateDtab() -- prior no success");
361 return;
362 }
363 if ( ! *src_dtab) {
364 CORE_TRACE("Leaving s_UpdateDtab() -- prior no dtab");
365 return;
366 }
367
368 /* Ignore header label if it was included. */
369 if (strncasecmp(src_dtab, DTAB_HDR_FIELD_NAME ":",
370 sizeof(DTAB_HDR_FIELD_NAME) + 1/*':'*/ - 1/*'\0'*/) == 0)
371 {
372 /* advance start past name, colon, and any whitespace */
373 src_dtab += sizeof(DTAB_HDR_FIELD_NAME) + 1/*':'*/ - 1/*'\0'*/;
374 while (*src_dtab == ' ' || *src_dtab == '\t') ++src_dtab;
375 }
376
377 /* Dtabs passed as query string parameter must be url-encoded, for example:
378 * from: "/lbsm/bounce=>/service/xyz"
379 * to: "%2Flbsm%2Fbounce%3D%3E%2Fservice%2Fxyz"
380 */
381 URL_Encode(src_dtab, strlen(src_dtab), &src_size,
382 enc_dtab, MAX_QRY_STR_LEN, &enc_size);
383 enc_dtab[enc_size] = NIL; /* not done by URL_Encode() */
384
385 /* If dtab already has an entry then append a semicolon and the new dtab. */
386 if (*dest_dtab_p && **dest_dtab_p) {
387 new_size = strlen(*dest_dtab_p) + 3/* "%3B" */ + strlen(enc_dtab) + 1;
388 new_dtab = (char*)realloc(*dest_dtab_p, new_size);
389 if (new_dtab) {
390 strcat(new_dtab, "%3B"); /* url-encoded ';' separator */
391 strcat(new_dtab, enc_dtab);
392 }
393 } else {
394 /* Dtab didn't exist yet, so just clone it. */
395 new_dtab = strdup(enc_dtab);
396 }
397 if ( ! new_dtab) {
398 *success_p = 0;
399 CORE_LOG_X(eNSub_Alloc, eLOG_Critical, "Couldn't alloc for dtab.");
400 CORE_TRACE("Leaving s_UpdateDtab() -- bad alloc");
401 return;
402 }
403
404 /* Update the caller's pointer. */
405 *dest_dtab_p = new_dtab;
406
407 CORE_TRACEF(("Leaving s_UpdateDtab() -- new dtab = \"%s\"", new_dtab));
408 }
409
410
s_GetReqMethod()411 static TReqMethod s_GetReqMethod()
412 {
413 char val[20];
414
415 if ( ! ConnNetInfo_GetValueInternal(REG_NAMERD_SECTION,
416 REG_NAMERD_API_REQ_KEY, val, sizeof(val)-1,
417 REG_NAMERD_API_REQ_DEF))
418 {
419 CORE_LOG_X(eNSub_Alloc, eLOG_Critical,
420 "Couldn't alloc for request method.");
421 return eReqMethod_Any;
422 }
423 if ( ! *val) return eReqMethod_Any;
424 if (strcasecmp(val, "GET") == 0) return eReqMethod_Get;
425 if (strcasecmp(val, "POST") == 0) return eReqMethod_Post;
426 if (strcasecmp(val, "GET11") == 0) return eReqMethod_Get11;
427 if (strcasecmp(val, "POST11") == 0) return eReqMethod_Post11;
428
429 return eReqMethod_Any;
430 }
431
432
s_GetScheme()433 static EBURLScheme s_GetScheme()
434 {
435 char val[12];
436
437 if ( ! ConnNetInfo_GetValueInternal(REG_NAMERD_SECTION,
438 REG_NAMERD_API_SCHEME_KEY, val, sizeof(val)-1,
439 REG_NAMERD_API_SCHEME_DEF))
440 {
441 CORE_LOG_X(eNSub_Alloc, eLOG_Critical,
442 "Couldn't alloc for scheme.");
443 return eURL_Unspec;
444 }
445 if ( ! *val) return eURL_Unspec;
446 if (strcasecmp(val, "http") == 0) return eURL_Http;
447 if (strcasecmp(val, "https") == 0) return eURL_Https;
448
449 return eURL_Unspec;
450 }
451
452
s_GetHttpProxy(char * host,size_t host_size,unsigned short * port_p)453 static int s_GetHttpProxy(char* host, size_t host_size, unsigned short* port_p)
454 {
455 char port_str[24];
456
457 /* Highest precedence is $http_proxy environment variable */
458 switch (LINKERD_GetHttpProxy(host, host_size, port_p)) {
459 case eLGHP_Success:
460 return 1;
461 case eLGHP_Fail:
462 CORE_LOG_X(eNSub_BadData, eLOG_Critical,
463 "Couldn't get Linkerd http_proxy.");
464 return 0;
465 case eLGHP_NotSet:
466 break;
467 }
468
469 if ( ! ConnNetInfo_GetValueInternal(REG_NAMERD_SECTION,
470 REG_NAMERD_PROXY_HOST_KEY, host, host_size - 1,
471 REG_NAMERD_PROXY_HOST_DEF) || ! *host)
472 {
473 CORE_LOG_X(eNSub_Alloc, eLOG_Critical,
474 "Couldn't set default http proxy host.");
475 return 0;
476 }
477
478 if ( ! ConnNetInfo_GetValueInternal(REG_NAMERD_SECTION,
479 REG_NAMERD_PROXY_PORT_KEY, port_str, sizeof(port_str) - 1,
480 REG_NAMERD_PROXY_PORT_DEF) ||
481 ! *port_str ||
482 sscanf(port_str, "%hu", port_p) != 1)
483 {
484 CORE_LOG_X(eNSub_Alloc, eLOG_Critical,
485 "Couldn't set default http proxy port.");
486 return 0;
487 }
488
489 return 1;
490 }
491
492
s_RemoveCand(struct SNAMERD_Data * data,size_t n,int free_info)493 static void s_RemoveCand(struct SNAMERD_Data* data, size_t n, int free_info)
494 {
495 CORE_TRACEF(("s_RemoveCand() Removing info " FMT_SIZE_T ": %p",
496 n, data->cand[n].info));
497
498 if (n >= data->n_cand) {
499 CORE_LOGF_X(eNSub_Logic, eLOG_Critical,
500 ("Unexpected: n(" FMT_SIZE_T ") >= data->n_cand("
501 FMT_SIZE_T ")", n, data->n_cand));
502 return;
503 }
504 if (free_info)
505 free((void*) data->cand[n].info);
506 if (n < --data->n_cand) {
507 memmove(data->cand + n, data->cand + n + 1,
508 (data->n_cand - n) * sizeof(*data->cand));
509 }
510 if (data->n_cand == 0)
511 data->done = 1;
512 }
513
514
s_RemoveStandby(struct SNAMERD_Data * data)515 static void s_RemoveStandby(struct SNAMERD_Data* data)
516 {
517 double max_rate = 0.0;
518 int all_standby = 1;
519 size_t i;
520
521 /* basic logic:
522 if any endpoints have rate >= LBSM_STANDBY_THRESHOLD
523 discard all endpoints with rate < LBSM_STANDBY_THRESHOLD
524 else
525 discard all endpoints with rate < highest rate
526 */
527
528 for (i = 0; i < data->n_cand; ++i) {
529
530 if (max_rate < data->cand[i].info->rate)
531 max_rate = data->cand[i].info->rate;
532
533 if (data->cand[i].info->rate >= LBSM_STANDBY_THRESHOLD)
534 all_standby = 0;
535 }
536
537 /* Loop from highest index to lowest to ensure well-defined behavior when
538 candidates are removed and to avoid memmove in s_RemoveCand(). */
539 for (i = data->n_cand; i > 0; ) {
540 --i; /* decrement here to avoid unsigned underflow */
541 if (data->cand[i].info->rate <
542 (all_standby ? max_rate : LBSM_STANDBY_THRESHOLD))
543 s_RemoveCand(data, i, 1);
544 }
545 }
546
547
s_AddServerInfo(struct SNAMERD_Data * data,SSERV_Info * info)548 static int/*bool*/ s_AddServerInfo(struct SNAMERD_Data* data, SSERV_Info* info)
549 {
550 const char* name = SERV_NameOfInfo(info);
551 size_t i;
552
553 /* First check if the new server info updates an existing one */
554 for (i = 0; i < data->n_cand; ++i) {
555 if (strcasecmp(name, SERV_NameOfInfo(data->cand[i].info)) == 0
556 && SERV_EqualInfo(info, data->cand[i].info))
557 {
558 /* Replace older version */
559 CORE_TRACE("Replaced older candidate version.");
560 free((void*) data->cand[i].info);
561 data->cand[i].info = info;
562 data->cand[i].status = info->rate;
563
564 return 1;
565 }
566 }
567
568 /* Grow candidates container at capacity - trigger growth when there's no
569 longer room for a new entry. */
570 if (data->a_cand == 0 || data->n_cand >= data->a_cand) {
571 size_t n = data->a_cand + 10;
572 SLB_Candidate* temp =
573 (SLB_Candidate*)realloc(data->cand, n * sizeof(*temp));
574 if ( ! temp) {
575 CORE_LOG_X(eNSub_Alloc, eLOG_Critical,
576 "Failed to reallocate memory for new candidates.");
577 return 0;
578 }
579 data->cand = temp;
580 data->a_cand = n;
581 }
582
583 /* Add the new service to the list */
584 #if defined(_DEBUG) && ! defined(NDEBUG)
585 {{
586 char hostbuf[40];
587 SOCK_ntoa(info->host, hostbuf, sizeof(hostbuf));
588 CORE_TRACEF(("Added candidate " FMT_SIZE_T
589 ": %s:%hu with server type '%s'.",
590 data->n_cand, hostbuf, info->port, SERV_TypeStr(info->type)));
591 }}
592 #endif
593 data->cand[data->n_cand].info = info;
594 data->cand[data->n_cand].status = info->rate;
595 data->n_cand++;
596 data->done = 0;
597
598 return 1;
599 }
600
601
602 /* Parse the "addrs[i].meta.expires" JSON from the namerd API, and adjust
603 it according to the local timezone/DST to get the UTC epoch time.
604 This function is not meant to be a generic ISO-8601 parser. It expects
605 the namerd API to return times in the format: "2017-03-29T23:02:55Z"
606 Unfortunately, strptime is not supported at all on Windows, and doesn't
607 support the "%z" format on Unix, so some custom parsing is required.
608 */
s_ParseExpires(time_t tt_now,const char * expires)609 static TNCBI_Time s_ParseExpires(time_t tt_now, const char* expires)
610 {
611 int exp_year, exp_mon, exp_mday, exp_hour, exp_min, exp_sec;
612 char exp_zulu;
613
614 if ( ! expires) {
615 CORE_LOG_X(eNSub_Logic, eLOG_Critical,
616 "Unexpected NULL 'expires' pointer.");
617 return 0;
618 }
619
620 if (sscanf(expires, "%d-%d-%dT%d:%d:%d%c", &exp_year, &exp_mon, &exp_mday,
621 &exp_hour, &exp_min, &exp_sec, &exp_zulu) != 7 ||
622 (exp_year < 2017 || exp_year > 9999) ||
623 (exp_mon < 1 || exp_mon > 12) ||
624 (exp_mday < 1 || exp_mday > 31) ||
625 (exp_hour < 0 || exp_hour > 23) ||
626 (exp_min < 0 || exp_min > 59) ||
627 (exp_sec < 0 || exp_sec > 60) || /* 60 for leap second */
628 exp_zulu != 'Z')
629 {
630 CORE_LOGF_X(eNSub_Json, eLOG_Error,
631 ("Unexpected JSON {\"addrs[i].meta.expires\"} "
632 "value '%s'.", expires));
633 return 0;
634 }
635
636 /* Get the UTC epoch time for the expires value. */
637 struct tm tm_expires;
638 tm_expires.tm_year = exp_year - 1900; /* years since 1900 */
639 tm_expires.tm_mon = exp_mon - 1; /* months since January: 0-11 */
640 tm_expires.tm_mday = exp_mday;
641 tm_expires.tm_hour = exp_hour;
642 tm_expires.tm_min = exp_min;
643 tm_expires.tm_sec = exp_sec;
644 tm_expires.tm_isdst = 0;
645 tm_expires.tm_wday = 0;
646 tm_expires.tm_yday = 0;
647
648 /* Adjust for time diff between local and UTC, which should
649 correspond to 3600 x (number of time zones from UTC),
650 i.e. diff between current TZ (UTC-12 to UTC+14) and UTC. */
651 double tdiff;
652 struct tm tm_now;
653 CORE_LOCK_WRITE;
654 tm_now = *gmtime(&tt_now);
655 CORE_UNLOCK;
656 tdiff = difftime(mktime(&tm_expires), mktime(&tm_now));
657 if (tdiff < -14.0 * 3600.0 || tdiff > 12.0 * 3600.0) {
658 int now_year = tm_now.tm_year + 1900;
659 int now_mon = tm_now.tm_mon + 1;
660 int now_mday = tm_now.tm_mday;
661 int now_hour = tm_now.tm_hour;
662 int now_min = tm_now.tm_min;
663 int now_sec = tm_now.tm_sec;
664 char now_str[40]; /* need 21 for 'yyyy-mm-ddThh:mm:ssZ' */
665 double td_diff, td_hour, td_min, td_sec;
666 const char* td_sign;
667
668 if ((now_year < 2017 || now_year > 9999) ||
669 (now_mon < 1 || now_mon > 12) ||
670 (now_mday < 1 || now_mday > 31) ||
671 (now_hour < 0 || now_hour > 23) ||
672 (now_min < 0 || now_min > 59) ||
673 (now_sec < 0 || now_sec > 60)) /* 60 for leap second */
674 {
675 CORE_LOGF_X(eNSub_Json, eLOG_Error,
676 ("Invalid 'struct tm' for current time (adjusted to UTC): {"
677 "tm_year = %d, tm_mon = %d, tm_mday = %d, "
678 "tm_hour = %d, tm_min = %d, tm_sec = %d, }.",
679 tm_now.tm_year + 1900, tm_now.tm_mon + 1, tm_now.tm_mday,
680 tm_now.tm_hour, tm_now.tm_min, tm_now.tm_sec));
681 return 0;
682 }
683 if (tdiff < 0.0) {
684 td_sign = "-";
685 td_diff = -tdiff;
686 } else {
687 td_sign = "";
688 td_diff = tdiff;
689 }
690 td_hour = td_diff / 3600.0;
691 td_diff -= td_hour * 3600.0;
692 td_min = td_diff / 60.0;
693 td_sec = td_diff - td_min * 60.0;
694 sprintf(now_str, "%d-%02d-%02dT%02d:%02d:%02dZ",
695 now_year, now_mon, now_mday, now_hour, now_min, now_sec);
696 CORE_LOGF_X(eNSub_Json, eLOG_Error,
697 ("Unexpected JSON {\"addrs[i].meta.expires\"} "
698 "value '%s' - excessive difference (%.0lfs = %s%.0lf:%.0lf:%.0lf) "
699 "from current time '%s'",
700 expires, tdiff, td_sign, td_hour, td_min, td_sec, now_str));
701 return 0;
702 }
703 TNCBI_Time timeval = (TNCBI_Time)((double) tt_now + tdiff);
704 /*CORE_TRACEF(
705 ("expires: %s tdiff %lf now " FMT_TIME_T " info->time %u",
706 expires, tdiff, tt_now, timeval));*/
707
708 return timeval;
709 }
710
711
712 /* Parsing the response requires having the entire response in
713 one buffer. Therefore, realloc as necessary.
714
715 If the realloc'd size significantly exceeds the needed size, be nice and
716 realloc it back down to match the needed size.
717 */
s_ReadFullResponse(CONN conn,char ** bufp,SConnNetInfo * net_info)718 static EIO_Status s_ReadFullResponse(CONN conn, char** bufp,
719 SConnNetInfo* net_info)
720 {
721 /* just try a limited number of buffer sizes, in a
722 roughly geometric sequence */
723 static size_t buf_len_steps[] = {3100, 100100, 3100100};
724 int max_steps = sizeof(buf_len_steps)/sizeof(buf_len_steps[0]);
725 size_t waste_threshold = 50100;
726 size_t total_got = 0;
727 size_t buf_len = 0, step_max = 0, step_got = 0;
728 char* new_buf;
729 int num_steps;
730 EIO_Status status = eIO_Unknown;
731
732 CORE_TRACE("Entering s_ReadFullResponse()");
733
734 if ( ! bufp) {
735 CORE_LOG_X(eNSub_Logic, eLOG_Critical,
736 "Unexpected NULL 'bufp' pointer.");
737 return eIO_Unknown;
738 }
739 if ( ! net_info) {
740 CORE_LOG_X(eNSub_Logic, eLOG_Critical,
741 "Unexpected NULL 'net_info' pointer.");
742 return eIO_Unknown;
743 }
744
745 for (num_steps = 0; num_steps < max_steps; ++num_steps) {
746
747 /* Expand the buffer to the next step size. */
748 buf_len = buf_len_steps[num_steps];
749 new_buf = (char*) realloc(*bufp, buf_len);
750 if ( ! new_buf) {
751 CORE_LOG_X(eNSub_Alloc, eLOG_Critical,
752 "Failed to allocate memory for response buffer.");
753 if (*bufp) {
754 free(*bufp);
755 *bufp = NULL;
756 }
757 CORE_TRACE("Leaving s_ReadFullResponse() -- bad alloc");
758 return eIO_Unknown;
759 }
760 *bufp = new_buf;
761
762 /* Read the next block of data. */
763 step_max = buf_len - total_got - 1/* zero-terminate */;
764 step_got = 0;
765 do {
766 size_t read_max, read_got;
767 read_max = step_max - step_got;
768 status = CONN_Read(conn, *bufp + total_got + step_got,
769 read_max, &read_got, eIO_ReadPlain);
770 step_got += read_got;
771 CORE_TRACEF((
772 "CONN_Read(step_max,step_got,read_max,read_got,status)=("
773 FMT_SIZE_T "," FMT_SIZE_T "," FMT_SIZE_T "," FMT_SIZE_T ",%s)",
774 step_max, step_got, read_max, read_got, IO_StatusStr(status)));
775 } while (step_got < step_max && status == eIO_Success);
776 total_got += step_got;
777
778 /* Need to increase buffer size? */
779 if (step_got == step_max && status == eIO_Success) {
780 continue;
781 }
782
783 /* Handle problems. */
784 if (status != eIO_Closed) {
785 CORE_LOGF_X(eNSub_HttpRead, eLOG_Error,
786 ("Read error: %s", IO_StatusStr(status)));
787 free(*bufp);
788 *bufp = NULL;
789 CORE_TRACE("Leaving s_ReadFullResponse() -- read problem 1");
790 return status;
791 }
792
793 /* All data has been fetched. */
794 CORE_TRACEF((
795 "All data fetched, (num_steps,buf_len,total_got,step_max,"
796 "step_got)=(%d," FMT_SIZE_T "," FMT_SIZE_T "," FMT_SIZE_T
797 "," FMT_SIZE_T ")", num_steps, buf_len, total_got, step_max,
798 step_got));
799 break;
800 }
801
802 /* See if the max buffer size was insufficient (this is extremely unlikely
803 and would suggest reviewing the design of this function). */
804 if (step_got == step_max && status == eIO_Success) {
805 CORE_LOG_X(eNSub_TooLong, eLOG_Error, "Insufficient buffer size.");
806 free(*bufp);
807 *bufp = NULL;
808 CORE_TRACE("Leaving s_ReadFullResponse() -- read problem 2");
809 return status;
810 }
811
812 (*bufp)[total_got] = NIL; /* zero-terminate */
813
814 /* Reduce the buffer size if there's a lot of wasted space. */
815 if (buf_len - total_got > waste_threshold) {
816 new_buf = (char*)realloc(*bufp, total_got + 1/* zero-terminate */);
817 if (new_buf) {
818 *bufp = new_buf;
819 }
820 }
821
822 CORE_TRACEF(("Got response: %s", *bufp));
823
824 CORE_TRACE("Leaving s_ReadFullResponse()");
825 return eIO_Success;
826 }
827
828
s_ParseResponse(SERV_ITER iter,CONN conn)829 static int/*bool*/ s_ParseResponse(SERV_ITER iter, CONN conn)
830 {
831 struct SNAMERD_Data* data = (struct SNAMERD_Data*) iter->data;
832 SConnNetInfo* net_info = data->net_info;
833 x_JSON_Value* root_value = NULL;
834 char* response = NULL;
835 int/*bool*/ retval = 0;
836
837 CORE_TRACE("Entering s_ParseResponse()");
838
839 if (eIO_Success == s_ReadFullResponse(conn, &response, net_info)) {
840 x_JSON_Object *root_obj;
841 x_JSON_Array *address_arr;
842 const char *success_type;
843 size_t it;
844
845 /* root object */
846 root_value = x_json_parse_string(response);
847 if ( ! root_value) {
848 CORE_LOG_X(eNSub_Json, eLOG_Error,
849 "Response couldn't be parsed as JSON.");
850 goto out;
851 }
852 if (x_json_value_get_type(root_value) != JSONObject) {
853 CORE_LOG_X(eNSub_Json, eLOG_Error,
854 "Response root name is not a JSON object.");
855 goto out;
856 }
857 root_obj = x_json_value_get_object(root_value);
858 if ( ! root_obj) {
859 CORE_LOG_X(eNSub_Json, eLOG_Error,
860 "Couldn't get JSON root object.");
861 goto out;
862 }
863 #if defined(_DEBUG) && ! defined(NDEBUG)
864 char json[9999];
865 if (x_json_serialize_to_buffer_pretty(
866 root_value, json, sizeof(json)-1) == JSONSuccess)
867 CORE_TRACEF(("Got JSON:\n%s", json));
868 else
869 CORE_LOG_X(eNSub_Json, eLOG_Error, "Couldn't serialize JSON");
870 #endif
871
872 /* top-level {"type" : "bound"} expected for successful connection */
873 success_type = x_json_object_get_string(root_obj, "type");
874 if ( ! success_type) {
875 CORE_LOG_X(eNSub_Json, eLOG_Error,
876 "Couldn't get JSON {\"type\"} value.");
877 goto out;
878 }
879 if (strcmp(success_type, "bound") != 0) {
880 CORE_LOGF_X(eNSub_NoService, eLOG_Error,
881 ("Service \"%s\" appears to be unknown.", iter->name));
882 goto out;
883 }
884
885 /* top-level {"addrs" : []} contains endpoint data */
886 address_arr = x_json_object_get_array(root_obj, "addrs");
887 if ( ! address_arr) {
888 CORE_LOG_X(eNSub_Json, eLOG_Error,
889 "Couldn't get JSON {\"addrs\"} array.");
890 goto out;
891 }
892
893 /* Note: top-level {"meta" : {}} not currently used */
894
895 /* Iterate through addresses, adding to "candidates". */
896 for (it = 0; it < x_json_array_get_count(address_arr); ++it) {
897 x_JSON_Object *address;
898 const char *host, *extra, *srv_type, *mode;
899 const char *cpfx, *ctype;
900 const char *local;
901 const char *priv;
902 const char *stateful;
903 char *server_description;
904 double rate;
905 int port;
906
907 /* JSON|SSERV_Info|variable mapping to format string:
908 meta.expires|time|time -------------------------------+
909 meta.stateful|mode|stateful ---------------------+ |
910 meta.rate|rate|rate ---------------------------+ | |
911 meta.mode|site|priv ----------------------+ | | |
912 meta.mode|site|local -------------------+ | | | |
913 meta.contentType|mime_*|cpfx,ctype | | | | |
914 meta.extra|extra|extra -------+ | | | | | |
915 port|port|port ------------+ | | | | | | |
916 ip|host|host -----------+ | | | | | | | |
917 meta.serviceType| | | | | | | | | |
918 type|srv_type ---+ | | | | | | | | |
919 [] [] [] [] [__] [][] [][] [] */
920 const char* descr_fmt = "%s %s:%u %s %s%s L=%s%s R=%s%s T=%u";
921 /* NOTE: Some fields must not be included in certain situations
922 because SERV_ReadInfoEx() does not expect them in those
923 situations, and if they are present then SERV_ReadInfoEx()
924 will prematurely terminate processing the description.
925 Specifically:
926 do not include when
927 -------------- ---------------------
928 P= type=DNS
929 S= type=DNS or type=HTTP
930 */
931
932 /* get a handle on the object for this iteration */
933 address = x_json_array_get_object(address_arr, it);
934 if ( ! address) {
935 CORE_LOG_X(eNSub_Json, eLOG_Error,
936 "Couldn't get JSON {\"addrs[i]\"} object.");
937 goto out;
938 }
939
940 /* SSERV_Info.host <=== addrs[i].ip */
941 host = x_json_object_get_string(address, "ip");
942 if ( ! host || ! *host) {
943 CORE_LOG_X(eNSub_Json, eLOG_Error,
944 "Couldn't get JSON {\"addrs[i].ip\"} value.");
945 goto out;
946 }
947
948 /* SSERV_Info.port <=== addrs[i].port */
949 /* Unfortunately the x_json_object_get_number() function does
950 not distinguish failure from a legitimate zero value. :-/
951 Therefore, first explicitly check for the value. */
952 if ( ! x_json_object_has_value_of_type(
953 (const x_JSON_Object *)address,
954 (const char *)"port", JSONNumber))
955 {
956 CORE_LOG_X(eNSub_Json, eLOG_Error,
957 "Couldn't get JSON {\"addrs.port\"} name.");
958 goto out;
959 }
960 port = (int)x_json_object_get_number(address, "port");
961 if (port <= 0 || port > 65535) {
962 CORE_LOGF_X(eNSub_Json, eLOG_Error,
963 ("Invalid JSON {\"addrs[i].port\"} value (%d).", port));
964 goto out;
965 }
966
967 ESERV_Type type;
968 /* SSERV_Info.type <=== addrs[i].meta.serviceType */
969 srv_type = x_json_object_dotget_string(address, "meta.serviceType");
970 if ( ! srv_type || ! *srv_type) {
971 type = SERV_GetImplicitServerType(iter->name);
972 srv_type = SERV_TypeStr(type);
973 } else if ( ! SERV_ReadType(srv_type, &type))
974 {
975 CORE_LOGF_X(eNSub_Json, eLOG_Error,
976 ("Unrecognized server type '%s'.", srv_type));
977 goto out;
978 }
979 /* Make sure the server type matches an allowed type. */
980 if (iter->types != fSERV_Any && !(iter->types & type))
981 {
982 CORE_TRACEF((
983 "Ignoring endpoint %s:%d with unallowed server type '%s'"
984 " - allowed types = 0x%lx.",
985 host, port, srv_type, (unsigned long) iter->types));
986 continue;
987 }
988 CORE_TRACEF((
989 "Parsed endpoint %s:%d with allowed server type '%s'.",
990 host, port, srv_type));
991
992 /* SSERV_Info.mode <=== addrs[i].meta.stateful */
993 if (x_json_object_dothas_value_of_type(address, "meta.stateful",
994 JSONBoolean))
995 {
996 int st = x_json_object_dotget_boolean(address, "meta.stateful");
997 if (st == 0) {
998 stateful = "";
999 } else if (st == 1) {
1000 stateful = " S=YES";
1001 } else {
1002 CORE_LOGF_X(eNSub_Json, eLOG_Error,
1003 ("Invalid JSON {\"addrs[i].meta.stateful\"} value (%d).",
1004 st));
1005 goto out;
1006 }
1007 } else {
1008 stateful = "";
1009 }
1010
1011 /* Make sure the JSON is stateless for a stateless SERV_ITER. */
1012 if ((iter->types & fSERV_Stateless) && stateful[0] != '\0') {
1013 CORE_TRACEF((
1014 "Ignoring stateful endpoint %s:%d in stateless search.",
1015 host, port));
1016 continue;
1017 }
1018
1019 /* SSERV_Info.site <=== addrs[i].meta.mode */
1020 local = "NO";
1021 priv = "";
1022 if (x_json_object_dothas_value_of_type(address, "meta.mode",
1023 JSONString))
1024 {
1025 mode = x_json_object_dotget_string(address, "meta.mode");
1026 if ( ! mode || ! *mode) {
1027 CORE_LOG_X(eNSub_Json, eLOG_Error,
1028 "Couldn't get JSON {\"addrs[i].meta.mode\"} value.");
1029 goto out;
1030 }
1031 if (strcmp(mode, "L") == 0) {
1032 local = "YES";
1033 } else if (strcmp(mode, "H") == 0) {
1034 local = "YES";
1035 priv = " P=YES";
1036 } else {
1037 CORE_LOGF_X(eNSub_Json, eLOG_Error,
1038 ("Unexpected JSON {\"addrs[i].meta.mode\"} value '%s'.",
1039 mode));
1040 goto out;
1041 }
1042 }
1043
1044 /* SSERV_Info.rate <=== addrs[i].meta.rate */
1045 if (x_json_object_dothas_value_of_type(address, "meta.rate",
1046 JSONNumber))
1047 {
1048 rate = x_json_object_dotget_number(address, "meta.rate");
1049 } else {
1050 rate = LBSM_DEFAULT_RATE;
1051 }
1052 /* verify magnitude */
1053 if (rate < SERV_MINIMAL_RATE || rate > SERV_MAXIMAL_RATE) {
1054 CORE_LOGF_X(eNSub_Json, eLOG_Error,
1055 ("Unexpected JSON {\"addrs[i].meta.rate\"} value '%lf'.",
1056 rate));
1057 goto out;
1058 }
1059 /* format with 3-digit precsion for standby; else 2-digits */
1060 char rate_str[12];
1061 sprintf(rate_str, "%.*lf",
1062 (rate < LBSM_STANDBY_THRESHOLD ? 3 : 2), rate);
1063
1064 /* SSERV_Info.time <=== addrs[i].meta.expires */
1065 time_t tt_now = time(0);
1066 TNCBI_Time timeval = (TNCBI_Time)(tt_now + LBSM_DEFAULT_TIME);
1067 if (x_json_object_dothas_value_of_type(address, "meta.expires",
1068 JSONString))
1069 {
1070 timeval = s_ParseExpires(tt_now,
1071 x_json_object_dotget_string(address, "meta.expires"));
1072 if ( ! timeval)
1073 goto out;
1074 } else {
1075 static void* s_Once = 0;
1076 if (CORE_Once(&s_Once)) {
1077 CORE_LOGF_X(eNSub_Json, eLOG_Trace,
1078 ("Missing JSON {\"addrs[i].meta.expires\"} - "
1079 "using current time (" FMT_TIME_T
1080 ") + default expiration "
1081 "(%u) = %u", tt_now, LBSM_DEFAULT_TIME, timeval));
1082 }
1083 }
1084
1085 /* SSERV_Info.mime_t
1086 SSERV_Info.mime_s
1087 SSERV_Info.mime_e <=== addrs[i].meta.contentType */
1088 ctype = x_json_object_dotget_string(address, "meta.contentType");
1089 cpfx = (ctype ? " C=" : "");
1090 ctype = (ctype ? ctype : "");
1091
1092 /* SSERV_Info.extra <=== addrs[i].meta.extra */
1093 extra = x_json_object_dotget_string(address, "meta.extra");
1094 if ( ! extra || ! *extra) {
1095 if (type & fSERV_Http) {
1096 CORE_LOG_X(eNSub_Json, eLOG_Trace,
1097 "Namerd API did not return a path in meta.extra "
1098 "JSON for an HTTP type server");
1099 extra = "/";
1100 } else if (type == fSERV_Ncbid) {
1101 CORE_LOG_X(eNSub_Json, eLOG_Trace,
1102 "Namerd API did not return args in meta.extra "
1103 "JSON for an NCBID type server");
1104 extra = "''";
1105 } else {
1106 extra = "";
1107 }
1108 }
1109
1110 /* Prepare description */
1111 size_t length;
1112 length = strlen(descr_fmt) + strlen(srv_type) + strlen(host) +
1113 5 /*length of port*/ + strlen(extra) + strlen(cpfx) +
1114 strlen(ctype) + strlen(local) + strlen(priv) +
1115 strlen(rate_str) + strlen(stateful) +
1116 20/*length of timeval*/;
1117 server_description = (char*)malloc(sizeof(char) * length);
1118 if ( ! server_description) {
1119 CORE_LOG_X(eNSub_Alloc, eLOG_Critical,
1120 "Couldn't alloc for server description.");
1121 goto out;
1122 }
1123 sprintf(server_description, descr_fmt, srv_type, host,
1124 port, extra, cpfx, ctype, local, priv, rate_str, stateful,
1125 timeval);
1126
1127 /* Parse description into SSERV_Info */
1128 CORE_TRACEF(
1129 ("Parsing server description: '%s'", server_description));
1130 SSERV_Info* info = SERV_ReadInfoEx(server_description,
1131 iter->ismask || iter->reverse_dns ? iter->name : "", 0);
1132 /*CORE_TRACEF(
1133 ("Parsed server description; expires=%u", info->time));*/
1134
1135 if ( ! info) {
1136 CORE_LOGF_X(eNSub_BadData, eLOG_Warning,
1137 ("Unable to add server info with description '%s'.",
1138 server_description));
1139 free(server_description);
1140 continue;
1141 }
1142 free(server_description);
1143 CORE_TRACEF(("Created info: %p", info));
1144
1145 /* Add new info to collection */
1146 if (s_AddServerInfo(data, info)) {
1147 retval = 1; /* at least one info added */
1148 } else {
1149 CORE_LOG_X(eNSub_Alloc, eLOG_Critical,
1150 "Unable to add server info.");
1151 CORE_TRACEF(("Freeing info: %p", info));
1152 free(info); /* not freed by failed s_AddServerInfo() */
1153 goto out;
1154 }
1155 }
1156
1157 /* Filter out standby endpoints */
1158 s_RemoveStandby(data);
1159 }
1160
1161 out:
1162 if (response)
1163 free(response);
1164 if (root_value)
1165 x_json_value_free(root_value);
1166 CORE_TRACE("Leaving s_ParseResponse()");
1167 return retval;
1168 }
1169
1170
1171 /* Parse buffer and extract DTab-Local header, if present. */
s_GetDtabHeaderFromBuf(const char * buf)1172 static char* s_GetDtabHeaderFromBuf(const char* buf)
1173 {
1174 char* start = (char*)buf;
1175 char* end;
1176 char* dup_hdr;
1177
1178 CORE_TRACEF(("Entering s_GetDtabHeaderFromBuf(\"%s\")", buf ? buf : ""));
1179
1180 if (start && strncasecmp(start, DTAB_HDR_FIELD_NAME ":",
1181 sizeof(DTAB_HDR_FIELD_NAME) + 1/*':'*/ - 1/*'\0'*/) == 0)
1182 {
1183 /* advance start past name, colon, and any whitespace */
1184 start += sizeof(DTAB_HDR_FIELD_NAME) + 1;
1185 while (*start == ' ' || *start == '\t') ++start;
1186
1187 /* advance end to \0 or eol (excluding eol) */
1188 end = start;
1189 while (*end && *end != '\r' && *end != '\n') ++end;
1190
1191 /* clone the header value */
1192 dup_hdr = (char*) malloc((size_t)(end - start + 1));
1193 if ( ! dup_hdr) {
1194 CORE_LOG_X(eNSub_Alloc, eLOG_Critical,
1195 "Couldn't alloc for dtab header value.");
1196 CORE_TRACE("Leaving s_GetDtabHeaderFromBuf() -- bad alloc");
1197 return NULL;
1198 }
1199 memcpy(dup_hdr, start, (size_t)(end - start));
1200 dup_hdr[end - start] = NIL;
1201 CORE_TRACEF((
1202 "Leaving s_GetDtabHeaderFromBuf() -- got dtab header \"%s\"",
1203 dup_hdr));
1204 return dup_hdr;
1205 }
1206
1207 CORE_TRACE("Leaving s_GetDtabHeaderFromBuf()");
1208 return NULL;
1209 }
1210
1211
1212 /* If there's a dtab in the user header, add it to the caller's dtab. */
s_UpdateDtabFromUserHeader(char ** dtab_p,int * success_p,SConnNetInfo * net_info)1213 static void s_UpdateDtabFromUserHeader(char** dtab_p, int* success_p,
1214 SConnNetInfo* net_info)
1215 {
1216 char* dtab = NULL;
1217
1218 CORE_TRACEF(("Entering s_UpdateDtabFromUserHeader(\"%s\") -- success=%d",
1219 net_info->http_user_header ? net_info->http_user_header : "",
1220 *success_p));
1221
1222 if ( ! *success_p) {
1223 CORE_TRACE("Leaving s_UpdateDtabFromUserHeader() -- prior no success");
1224 return;
1225 }
1226
1227 dtab = s_GetDtabHeaderFromBuf(net_info->http_user_header);
1228 if (dtab) {
1229 /*ConnNetInfo_DeleteUserHeader(net_info, DTAB_HDR_FIELD_NAME);*/
1230 s_UpdateDtab(dtab_p, dtab, success_p);
1231 free(dtab);
1232 }
1233
1234 CORE_TRACE("Leaving s_UpdateDtabFromUserHeader()");
1235 }
1236
1237
1238 /* If there's a dtab in the registry, add it to the caller's dtab. */
s_UpdateDtabFromRegistry(char ** dtab_p,int * success_p,const char * service)1239 static void s_UpdateDtabFromRegistry(char** dtab_p, int* success_p,
1240 const char* service)
1241 {
1242 char val[MAX_QRY_STR_LEN + 1];
1243
1244 CORE_TRACEF(("Entering s_UpdateDtabFromRegistry(\"%s\") -- success=%d",
1245 service ? service : "", *success_p));
1246
1247 if ( ! *success_p) {
1248 CORE_TRACE("Leaving s_UpdateDtabFromRegistry() -- prior no success");
1249 return;
1250 }
1251
1252 if ( ! ConnNetInfo_GetValueInternal(REG_NAMERD_SECTION,
1253 REG_NAMERD_DTAB_KEY, val, MAX_QRY_STR_LEN,
1254 REG_NAMERD_DTAB_DEF))
1255 {
1256 *success_p = 0;
1257 CORE_LOG_X(eNSub_Alloc, eLOG_Critical,
1258 "Couldn't alloc for dtab from registry.");
1259 CORE_TRACE("Leaving s_UpdateDtabFromRegistry() -- bad alloc");
1260 return;
1261 }
1262
1263 s_UpdateDtab(dtab_p, val, success_p);
1264
1265 CORE_TRACE("Leaving s_UpdateDtabFromRegistry()");
1266 }
1267
1268
1269 /* If net_info->http_user_header contains a DTab-Local header, that value
1270 * must be moved to net_info->args, which in turn populates the "dtab"
1271 * parameter in the HTTP query string. It must not be in a header because
1272 * if it was, it would apply to the namerd service itself, not the requested
1273 * service. Instead, the namerd service uses the dtab param for resolution.
1274 *
1275 * Thus, this function builds the dtab param from (highest priority first):
1276 * * CONN_DTAB environment variable
1277 * * DTab-Local header in net_info->http_user_header
1278 */
s_ProcessDtab(SConnNetInfo * net_info,SERV_ITER iter)1279 static int/*bool*/ s_ProcessDtab(SConnNetInfo* net_info, SERV_ITER iter)
1280 {
1281 #define DTAB_ARGS_SEP "dtab"
1282 int/*bool*/ success = 1;
1283 char* dtab = NULL;
1284
1285 CORE_TRACE("Entering s_ProcessDtab()");
1286
1287 /* Dtab precedence (highest first): registry > user_header */
1288 s_UpdateDtabFromRegistry(&dtab, &success, iter->name);
1289 s_UpdateDtabFromUserHeader(&dtab, &success, net_info);
1290
1291 if (success && dtab
1292 && !ConnNetInfo_AppendArg(net_info, DTAB_ARGS_SEP, dtab)) {
1293 CORE_LOG_X(eNSub_TooLong, eLOG_Error, "Dtab too long.");
1294 success = 0;
1295 }
1296
1297 if (dtab)
1298 free(dtab);
1299
1300 CORE_TRACE("Leaving s_ProcessDtab()");
1301 return success;
1302 #undef DTAB_ARGS_SEP
1303 }
1304
1305
s_ParseHeader(const char * header,void * iter,int server_error)1306 static EHTTP_HeaderParse s_ParseHeader(const char* header,
1307 void* iter,
1308 int server_error)
1309 {
1310 struct SNAMERD_Data* data = (struct SNAMERD_Data*)((SERV_ITER) iter)->data;
1311 int code = 0/*success code if any*/;
1312
1313 CORE_TRACEF(("Entering s_ParseHeader(\"%s\")", header));
1314
1315 if (server_error == 400 || server_error == 403 || server_error == 404) {
1316 data->fail = 1/*true*/;
1317 } else if (sscanf(header, "%*s %d", &code) < 1) {
1318 data->eof = 1/*true*/;
1319 CORE_TRACE("Leaving s_ParseHeader() -- eof=true");
1320 return eHTTP_HeaderError;
1321 }
1322
1323 /* check for empty document */
1324 if (code == 204)
1325 data->eof = 1/*true*/;
1326
1327 CORE_TRACE("Leaving s_ParseHeader()");
1328 return eHTTP_HeaderSuccess;
1329 }
1330
1331
s_Adjust(SConnNetInfo * net_info,void * iter,unsigned int unused)1332 static int/*bool*/ s_Adjust(SConnNetInfo* net_info,
1333 void* iter,
1334 unsigned int unused)
1335 {
1336 struct SNAMERD_Data* data = (struct SNAMERD_Data*)((SERV_ITER) iter)->data;
1337 return data->fail ? 0/*no more tries*/ : 1/*may try again*/;
1338 }
1339
1340
s_Resolve(SERV_ITER iter)1341 static int/*bool*/ s_Resolve(SERV_ITER iter)
1342 {
1343 struct SNAMERD_Data* data = (struct SNAMERD_Data*) iter->data;
1344 SConnNetInfo* net_info = data->net_info;
1345 CONNECTOR c = NULL;
1346 CONN conn = NULL;
1347 int/*bool*/ retval = 0;
1348
1349 CORE_TRACE("Entering s_Resolve()");
1350
1351 if (data->eof | data->fail) {
1352 CORE_LOG_X(eNSub_Logic, eLOG_Critical,
1353 "Unexpected non-zero 'data->eof | data->fail'.");
1354 return 0;
1355 }
1356
1357 /* Handle DTAB, if present. */
1358 s_ProcessDtab(net_info, iter);
1359
1360 /* Create connector and connection, and fetch and parse the response. */
1361 if (s_CONN_Create(iter, &c, &conn) == eIO_Success) {
1362 retval = s_ParseResponse(iter, conn);
1363 }
1364 s_CONN_Destroy(&c, &conn);
1365
1366 CORE_TRACE("Leaving s_Resolve()");
1367 return retval;
1368 }
1369
1370
s_IsUpdateNeeded(TNCBI_Time now,struct SNAMERD_Data * data)1371 static int/*bool*/ s_IsUpdateNeeded(TNCBI_Time now, struct SNAMERD_Data *data)
1372 {
1373 /* Note: Because the namerd API does not supply rate data for many
1374 of the services it resolves, a rate-based algorithm can't be used.
1375 It could end up in an infinite loop because the total could be zero,
1376 thus causing immediate repeated updates of the same server, forever.
1377 Therefore, use a simpler algorithm that is just:
1378 An update is needed if:
1379 * there are no candidates; or
1380 * any candidates are expired.
1381 */
1382 int any_expired = 0;
1383 size_t i;
1384
1385 /* Loop from highest index to lowest to ensure well-defined behavior when
1386 candidates are removed and to avoid memmove in s_RemoveCand(). */
1387 for (i = data->n_cand; i > 0; ) {
1388 --i; /* decrement here to avoid unsigned underflow */
1389 const SSERV_Info* info = data->cand[i].info;
1390 if (info->time < now) {
1391 #if defined(_DEBUG) && ! defined(NDEBUG)
1392 TNCBI_Time tnow = (TNCBI_Time) time(0);
1393 CORE_TRACE("Endpoint expired:");
1394 CORE_TRACEF((
1395 " info->time (%u) < iter->time (%u) now (%u)",
1396 info->time, now, tnow));
1397 CORE_TRACEF((
1398 " iter->time - info->time (%d) now - iter->time (%d)",
1399 now - info->time, tnow - now));
1400 #endif
1401 any_expired = 1;
1402 s_RemoveCand(data, i, 1);
1403 }
1404 }
1405
1406 return any_expired || data->n_cand == 0;
1407 }
1408
1409
s_GetCandidate(void * user_data,size_t n)1410 static SLB_Candidate* s_GetCandidate(void* user_data, size_t n)
1411 {
1412 struct SNAMERD_Data* data = (struct SNAMERD_Data*) user_data;
1413 return n < data->n_cand ? &data->cand[n] : 0;
1414 }
1415
1416
s_GetNextInfo(SERV_ITER iter,HOST_INFO * host_info)1417 static SSERV_Info* s_GetNextInfo(SERV_ITER iter, HOST_INFO* host_info)
1418 {
1419 struct SNAMERD_Data* data = NULL;
1420 SSERV_Info* info;
1421 size_t n;
1422
1423 CORE_TRACE("Entering s_GetNextInfo()");
1424
1425 if ( ! iter) {
1426 CORE_LOG_X(eNSub_Logic, eLOG_Critical,
1427 "Unexpected NULL 'iter' pointer.");
1428 return NULL;
1429 }
1430 if ( ! iter->data) {
1431 CORE_LOG_X(eNSub_Logic, eLOG_Critical,
1432 "Unexpected NULL 'iter->data' pointer.");
1433 return NULL;
1434 }
1435 data = (struct SNAMERD_Data*) iter->data;
1436
1437 if (data->n_cand < 1 && data->done) {
1438 data->done = 0;
1439 CORE_TRACE("Leaving s_GetNextInfo() -- end of candidates");
1440 return NULL;
1441 }
1442
1443 if (s_IsUpdateNeeded(iter->time, data)) {
1444 if ( ! (data->eof | data->fail)) {
1445 s_Resolve(iter);
1446 if (data->n_cand < 1) {
1447 data->done = 1;
1448 CORE_TRACE("Leaving s_GetNextInfo() -- resolved no candidates");
1449 return NULL;
1450 }
1451 }
1452 }
1453
1454 /* Pick a randomized candidate. */
1455 n = LB_Select(iter, data, s_GetCandidate, NAMERD_LOCAL_BONUS);
1456 info = (SSERV_Info*) data->cand[n].info;
1457 info->rate = data->cand[n].status;
1458
1459 /* Remove returned info */
1460 s_RemoveCand(data, n, 0);
1461
1462 if (host_info)
1463 *host_info = NULL;
1464
1465 CORE_TRACE("Leaving s_GetNextInfo()");
1466 return info;
1467 }
1468
1469
s_Reset(SERV_ITER iter)1470 static void s_Reset(SERV_ITER iter)
1471 {
1472 struct SNAMERD_Data* data = NULL;
1473
1474 CORE_TRACE("Entering s_Reset()");
1475
1476 if ( ! iter) {
1477 CORE_LOG_X(eNSub_Logic, eLOG_Critical,
1478 "Unexpected NULL 'iter' pointer.");
1479 return;
1480 }
1481 data = (struct SNAMERD_Data*) iter->data;
1482 if (data) {
1483 data->eof = data->fail = data->done = 0/*false*/;
1484 if (data->cand) {
1485 size_t i;
1486 if (data->n_cand > data->a_cand) {
1487 CORE_LOGF_X(eNSub_Logic, eLOG_Critical,
1488 ("Unexpected: data->n_cand(" FMT_SIZE_T ") > data->a_cand("
1489 FMT_SIZE_T ")", data->n_cand, data->a_cand));
1490 return;
1491 }
1492 for (i = 0; i < data->n_cand; ++i) {
1493 CORE_TRACEF(("Freeing available info " FMT_SIZE_T ": %p",
1494 i, data->cand[i].info));
1495 free((void*) data->cand[i].info);
1496 }
1497 data->n_cand = 0;
1498 }
1499 }
1500
1501 CORE_TRACE("Leaving s_Reset()");
1502 }
1503
1504
s_Close(SERV_ITER iter)1505 static void s_Close(SERV_ITER iter)
1506 {
1507 struct SNAMERD_Data* data = (struct SNAMERD_Data*) iter->data;
1508
1509 CORE_TRACE("Entering s_Close()");
1510
1511 /* Make sure s_Reset() has been called - it frees info structs. */
1512 s_Reset(iter);
1513
1514 if (data->cand)
1515 free(data->cand);
1516
1517 ConnNetInfo_Destroy(data->net_info);
1518 free(data);
1519 iter->data = NULL;
1520
1521 CORE_TRACE("Leaving s_Close()");
1522 }
1523
1524
1525 /***********************************************************************
1526 * EXTERNAL
1527 ***********************************************************************/
1528
SERV_NAMERD_Open(SERV_ITER iter,const SConnNetInfo * net_info,SSERV_Info ** info)1529 extern const SSERV_VTable* SERV_NAMERD_Open(SERV_ITER iter,
1530 const SConnNetInfo* net_info,
1531 SSERV_Info** info)
1532 {
1533 struct SNAMERD_Data* data;
1534 char namerd_env[32];
1535 char namerd_args[(CONN_PATH_LEN+1)/2];
1536
1537 CORE_TRACEF(("Entering SERV_NAMERD_Open(\"%s\")", iter->name));
1538
1539 s_Init();
1540
1541 if ( ! iter) {
1542 CORE_LOG_X(eNSub_Logic, eLOG_Critical,
1543 "Unexpected NULL 'iter' pointer.");
1544 return NULL;
1545 }
1546
1547 /* Check that service name is provided - otherwise there is nothing to
1548 * search for. */
1549 if ( ! iter->name) {
1550 CORE_LOG_X(eNSub_BadData, eLOG_Error,
1551 "Unexpected NULL 'iter->name'.");
1552 CORE_TRACE("Leaving SERV_NAMERD_Open() -- fail, no service name");
1553 return NULL;
1554 }
1555 if ( ! *iter->name) {
1556 CORE_LOG_X(eNSub_BadData, eLOG_Error,
1557 "Unexpected empty 'iter->name'.");
1558 CORE_TRACE("Leaving SERV_NAMERD_Open() -- fail, empty service name");
1559 return NULL;
1560 }
1561
1562 /* Prohibit catalog-prefixed services, e.g. "/lbsm/<svc>" */
1563 if (iter->name[0] == '/') {
1564 CORE_LOGF_X(eNSub_BadData, eLOG_Error,
1565 ("Invalid service name \"%s\" - must not begin with '/'.",
1566 iter->name));
1567 CORE_TRACE("Leaving SERV_NAMERD_Open() -- fail, catalog prefix");
1568 return NULL;
1569 }
1570
1571 /* Check that iter is not a mask. */
1572 if (iter->ismask) {
1573 CORE_LOG_X(eNSub_BadData, eLOG_Error,
1574 "NAMERD doesn't support masks.");
1575 CORE_TRACE("Leaving SERV_NAMERD_Open() -- fail, iter is a mask");
1576 return NULL;
1577 }
1578
1579 if ( ! (data = (struct SNAMERD_Data*) calloc(1, sizeof(*data)))) {
1580 CORE_LOG_X(eNSub_Alloc, eLOG_Critical,
1581 "Could not allocate for SNAMERD_Data.");
1582 CORE_TRACE("Leaving SERV_NAMERD_Open() -- fail, bad alloc");
1583 return NULL;
1584 }
1585 iter->data = data;
1586
1587 /* Make sure a net_info exists. */
1588 SConnNetInfo* new_net_info = NULL;
1589 if ( ! net_info) {
1590 new_net_info = ConnNetInfo_CreateInternal(iter->name);
1591 if ( ! new_net_info) {
1592 CORE_LOG_X(eNSub_Alloc, eLOG_Critical, "Couldn't create net_info.");
1593 s_Close(iter);
1594 CORE_TRACE("Leaving SERV_NAMERD_Open() -- fail, no new net_info");
1595 return NULL;
1596 }
1597 data->net_info = ConnNetInfo_Clone(new_net_info);
1598 } else {
1599 data->net_info = ConnNetInfo_Clone(net_info);
1600 }
1601 if ( ! data->net_info) {
1602 CORE_LOG_X(eNSub_Alloc, eLOG_Critical, "Couldn't clone net_info.");
1603 if (new_net_info) {
1604 ConnNetInfo_Destroy(new_net_info);
1605 }
1606 s_Close(iter);
1607 CORE_TRACE("Leaving SERV_NAMERD_Open() -- fail, no net_info clone");
1608 return NULL;
1609 }
1610 if (new_net_info) {
1611 ConnNetInfo_Destroy(new_net_info);
1612 }
1613
1614 if ( ! ConnNetInfo_SetupStandardArgs(data->net_info, iter->name)) {
1615 CORE_LOG_X(eNSub_BadData, eLOG_Critical,
1616 "Couldn't set up standard args.");
1617 s_Close(iter);
1618 CORE_TRACE("Leaving SERV_NAMERD_Open() -- fail, standard args");
1619 return NULL;
1620 }
1621
1622 if (g_NCBI_ConnectRandomSeed == 0) {
1623 g_NCBI_ConnectRandomSeed = iter->time ^ NCBI_CONNECT_SRAND_ADDEND;
1624 srand(g_NCBI_ConnectRandomSeed);
1625 }
1626
1627 data->net_info->req_method = s_GetReqMethod();
1628 data->net_info->scheme = s_GetScheme();
1629 data->net_info->port = 0; /* namerd doesn't support a port */
1630 data->net_info->host[0] = NIL;
1631 data->net_info->path[0] = NIL;
1632
1633 if ( ! s_GetHttpProxy(data->net_info->http_proxy_host,
1634 sizeof(data->net_info->http_proxy_host),
1635 &data->net_info->http_proxy_port))
1636 {
1637 s_Close(iter);
1638 CORE_TRACE("Leaving SERV_NAMERD_Open() -- fail, http_proxy");
1639 return NULL;
1640 }
1641
1642 if ( ! ConnNetInfo_GetValueInternal(REG_NAMERD_SECTION,
1643 REG_NAMERD_API_HOST_KEY, data->net_info->host,
1644 sizeof(data->net_info->host), REG_NAMERD_API_HOST_DEF))
1645 {
1646 data->net_info->host[0] = NIL;
1647 }
1648 if ( ! ConnNetInfo_GetValueInternal(REG_NAMERD_SECTION,
1649 REG_NAMERD_API_PATH_KEY, data->net_info->path,
1650 sizeof(data->net_info->path), REG_NAMERD_API_PATH_DEF))
1651 {
1652 data->net_info->path[0] = NIL;
1653 }
1654 if (ConnNetInfo_GetValueInternal(REG_NAMERD_SECTION,
1655 REG_NAMERD_API_ENV_KEY, namerd_env, sizeof(namerd_env),
1656 REG_NAMERD_API_ENV_DEF))
1657 {
1658 if (strlen(data->net_info->path) + 1 + strlen(namerd_env) <
1659 sizeof(data->net_info->path))
1660 {
1661 strcat(data->net_info->path, "/");
1662 strcat(data->net_info->path, namerd_env);
1663 }
1664 }
1665 if ( ! ConnNetInfo_GetValueInternal(REG_NAMERD_SECTION,
1666 REG_NAMERD_API_ARGS_KEY, namerd_args, sizeof(namerd_args),
1667 REG_NAMERD_API_ARGS_DEF)
1668 || strlen(namerd_args) + strlen(iter->name) >= sizeof(namerd_args))
1669 {
1670 ConnNetInfo_SetArgs(data->net_info, 0);
1671 } else {
1672 strcat(namerd_args, iter->name);
1673 ConnNetInfo_SetArgs(data->net_info, namerd_args);
1674 }
1675
1676 if (iter->types & fSERV_Stateless)
1677 data->net_info->stateless = 1/*true*/;
1678 if ((iter->types & fSERV_Firewall) && !data->net_info->firewall)
1679 data->net_info->firewall = eFWMode_Adaptive;
1680
1681 s_Resolve(iter);
1682
1683 if (!data->n_cand && (data->fail
1684 || !(data->net_info->stateless &&
1685 data->net_info->firewall))) {
1686 s_Close(iter);
1687 CORE_TRACE("Leaving SERV_NAMERD_Open() -- fail, stateless, firewall");
1688 return NULL;
1689 }
1690
1691 /* call GetNextInfo subsequently if info is actually needed */
1692 if (info)
1693 *info = NULL;
1694
1695 CORE_TRACE("Leaving SERV_NAMERD_Open()");
1696 return &s_op;
1697 }
1698
1699
SERV_NAMERD_SetConnectorSource(const char * mock_body)1700 extern int SERV_NAMERD_SetConnectorSource(const char* mock_body)
1701 {
1702 s_Init();
1703
1704 if ( ! mock_body || ! *mock_body) {
1705 s_CreateConnector = s_CreateConnectorHttp;
1706 return 1;
1707 }
1708 s_mock_body = mock_body;
1709 s_CreateConnector = s_CreateConnectorMemory;
1710 return 1;
1711 }
1712