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