/** bits for the offset */
#define RET_OFFSET_MASK (((unsigned)(~LDNS_WIREPARSE_MASK))>>LDNS_WIREPARSE_SHIFT)
/** return an error */
#define RET_ERR(e, off) ((int)(((e)&LDNS_WIREPARSE_MASK)|(((off)&RET_OFFSET_MASK)<<LDNS_WIREPARSE_SHIFT)))
/** Move parse error but keep its ID */
#define RET_ERR_SHIFT(e, move) RET_ERR(LDNS_WIREPARSE_ERROR(e), LDNS_WIREPARSE_OFFSET(e)+(move));
/*
* No special care is taken, all dots are translated into
* label separators.
* @param rel: true if the domain is not absolute (not terminated in .).
* The output is then still terminated with a '0' rootlabel.
*/
static int sldns_str2wire_dname_buf_rel(const char* str, uint8_t* buf,
size_t* olen, int* rel)
{
size_t len;
const char *s;
uint8_t *q, *pq, label_len;
if(rel) *rel = 0;
len = strlen((char*)str);
/* octet representation can make strings a lot longer than actual length */
if (len > LDNS_MAX_DOMAINLEN * 4) {
return RET_ERR(LDNS_WIREPARSE_ERR_DOMAINNAME_OVERFLOW, 0);
}
if (0 == len) {
return RET_ERR(LDNS_WIREPARSE_ERR_DOMAINNAME_UNDERFLOW, 0);
}
/* s is on the current character in the string
* pq points to where the labellength is going to go
* label_len keeps track of the current label's length
* q builds the dname inside the buf array
*/
len = 0;
if(*olen < 1)
return RET_ERR(LDNS_WIREPARSE_ERR_BUFFER_TOO_SMALL, 0);
q = buf+1;
pq = buf;
label_len = 0;
for (s = str; *s; s++, q++) {
if (q >= buf + *olen)
return RET_ERR(LDNS_WIREPARSE_ERR_BUFFER_TOO_SMALL, q-buf);
if (q >= buf + LDNS_MAX_DOMAINLEN)
return RET_ERR(LDNS_WIREPARSE_ERR_DOMAINNAME_OVERFLOW, q-buf);
switch (*s) {
case '.':
if (label_len > LDNS_MAX_LABELLEN) {
return RET_ERR(LDNS_WIREPARSE_ERR_LABEL_OVERFLOW, q-buf);
}
if (label_len == 0) {
return RET_ERR(LDNS_WIREPARSE_ERR_EMPTY_LABEL, q-buf);
}
len += label_len + 1;
*q = 0;
*pq = label_len;
label_len = 0;
pq = q;
break;
case '\\':
/* octet value or literal char */
s += 1;
if (!sldns_parse_escape(q, &s)) {
*q = 0;
return RET_ERR(LDNS_WIREPARSE_ERR_SYNTAX_BAD_ESCAPE, q-buf);
}
s -= 1;
label_len++;
break;
default:
*q = (uint8_t)*s;
label_len++;
}
}
/* add root label if last char was not '.' */
if(label_len != 0) {
if(rel) *rel = 1;
if (q >= buf + *olen)
return RET_ERR(LDNS_WIREPARSE_ERR_BUFFER_TOO_SMALL, q-buf);
if (q >= buf + LDNS_MAX_DOMAINLEN) {
return RET_ERR(LDNS_WIREPARSE_ERR_DOMAINNAME_OVERFLOW, q-buf);
}
if (label_len > LDNS_MAX_LABELLEN) {
return RET_ERR(LDNS_WIREPARSE_ERR_LABEL_OVERFLOW, q-buf);
}
if (label_len == 0) { /* label_len 0 but not . at end? */
return RET_ERR(LDNS_WIREPARSE_ERR_EMPTY_LABEL, q-buf);
}
len += label_len + 1;
*pq = label_len;
*q = 0;
}
len++;
*olen = len;
if (strlen(token) > 0 && !isdigit((unsigned char)token[0])) {
*not_there = 1;
/* ah, it's not there or something */
if (default_ttl == 0) {
*ttl = LDNS_DEFAULT_TTL;
} else {
*ttl = default_ttl;
}
}
return LDNS_WIREPARSE_ERR_OK;
}
/** read class */
static int
rrinternal_get_class(sldns_buffer* strbuf, char* token, size_t token_len,
int* not_there, uint16_t* cl)
{
/* if 'not_there' then we got token from previous parse routine */
if(!*not_there) {
/* parse new token for class */
if(sldns_bget_token(strbuf, token, "\t\n ", token_len) == -1) {
return RET_ERR(LDNS_WIREPARSE_ERR_SYNTAX_CLASS,
sldns_buffer_position(strbuf));
}
} else *not_there = 0;
*cl = sldns_get_rr_class_by_name(token);
/* class can be left out too, assume IN, current token must be type */
if(*cl == 0 && strcmp(token, "CLASS0") != 0) {
*not_there = 1;
*cl = LDNS_RR_CLASS_IN;
}
return LDNS_WIREPARSE_ERR_OK;
}
/** read type */
static int
rrinternal_get_type(sldns_buffer* strbuf, char* token, size_t token_len,
int* not_there, uint16_t* tp)
{
/* if 'not_there' then we got token from previous parse routine */
if(!*not_there) {
/* parse new token for type */
if(sldns_bget_token(strbuf, token, "\t\n ", token_len) == -1) {
return RET_ERR(LDNS_WIREPARSE_ERR_SYNTAX_TYPE,
sldns_buffer_position(strbuf));
}
}
*tp = sldns_get_rr_type_by_name(token);
if(*tp == 0 && strcmp(token, "TYPE0") != 0) {
return RET_ERR(LDNS_WIREPARSE_ERR_SYNTAX_TYPE,
sldns_buffer_position(strbuf));
}
return LDNS_WIREPARSE_ERR_OK;
}
/** put type, class, ttl into rr buffer */
static int
rrinternal_write_typeclassttl(sldns_buffer* strbuf, uint8_t* rr, size_t len,
size_t dname_len, uint16_t tp, uint16_t cl, uint32_t ttl, int question)
{
if(question) {
/* question is : name, type, class */
if(dname_len + 4 > len)
return RET_ERR(LDNS_WIREPARSE_ERR_BUFFER_TOO_SMALL,
sldns_buffer_position(strbuf));
sldns_write_uint16(rr+dname_len, tp);
sldns_write_uint16(rr+dname_len+2, cl);
return LDNS_WIREPARSE_ERR_OK;
}
/** find delimiters for type */
static const char*
rrinternal_get_delims(sldns_rdf_type rdftype, size_t r_cnt, size_t r_max)
{
switch(rdftype) {
case LDNS_RDF_TYPE_B64 :
case LDNS_RDF_TYPE_HEX : /* These rdf types may con- */
case LDNS_RDF_TYPE_LOC : /* tain whitespace, only if */
case LDNS_RDF_TYPE_WKS : /* it is the last rd field. */
case LDNS_RDF_TYPE_IPSECKEY :
case LDNS_RDF_TYPE_NSEC : if (r_cnt == r_max - 1) {
return "\n";
}
break;
default : break;
}
return "\n\t ";
}
case LDNS_RDF_TYPE_HEX:
case LDNS_RDF_TYPE_B64:
/* When this is the last rdata field, then the
* rest should be read in (cause then these
* rdf types may contain spaces). */
if(r_cnt == r_max - 1) {
size_t tlen = strlen(token);
(void)sldns_bget_token(strbuf, token+tlen, "\n",
token_len - tlen);
}
break;
default:
break;
}
/** Add space and one more rdf token onto the existing token string. */
static int
sldns_affix_token(sldns_buffer* strbuf, char* token, size_t* token_len,
int* quoted, int* parens, size_t* pre_data_pos,
const char* delimiters, sldns_rdf_type rdftype, size_t* token_strlen)
{
size_t addlen = *token_len - *token_strlen;
size_t addstrlen = 0;
/* add space */
/* when addlen < 2, the token buffer is full considering the NULL byte
* from strlen and will lead to buffer overflow with the second
* assignment below. */
if(addlen < 2) return 0;
token[*token_strlen] = ' ';
token[++(*token_strlen)] = 0;
nparams += 1;
if (nparams >= MAX_NUMBER_OF_SVCPARAMS)
return LDNS_WIREPARSE_ERR_SVCB_TOO_MANY_PARAMS;
}
/* In draft-ietf-dnsop-svcb-https-06 Section 7:
*
* In wire format, the keys are represented by their numeric
* values in network byte order, concatenated in ascending order.
*/
qsort((void *)svcparams
,nparams
,sizeof(uint8_t*)
,sldns_str2wire_svcparam_key_cmp);
/* The code below revolves around semantic errors in the SVCParam set.
* So long as we do not distinguish between running Unbound as a primary
* or as a secondary, we default to secondary behavior and we ignore the
* semantic errors. */
#ifdef SVCB_SEMANTIC_ERRORS
{
uint8_t* mandatory = NULL;
/* In draft-ietf-dnsop-svcb-https-06 Section 7:
*
* Keys (...) MUST NOT appear more than once.
*
* If they key has already been seen, we have a duplicate
*/
for(i=0; i < nparams; i++) {
uint16_t key = sldns_read_uint16(svcparams[i]);
if(i + 1 < nparams && key == sldns_read_uint16(svcparams[i+1]))
return LDNS_WIREPARSE_ERR_SVCB_DUPLICATE_KEYS;
if(key == SVCB_KEY_MANDATORY)
mandatory = svcparams[i];
}
/* Verify that all the SvcParamKeys in mandatory are present */
if(mandatory) {
/* Divide by sizeof(uint16_t)*/
uint16_t mandatory_nkeys = sldns_read_uint16(mandatory + 2) / sizeof(uint16_t);
/* Guaranteed by sldns_str2wire_svcparam_key_value */
assert(mandatory_nkeys > 0);
for(i=0; i < mandatory_nkeys; i++) {
uint16_t mandatory_key = sldns_read_uint16(
mandatory
+ 2 * sizeof(uint16_t)
+ i * sizeof(uint16_t));
uint8_t found = 0;
size_t j;
/* because number of fields can be variable, we can't rely on
* _maximum() only */
for(r_cnt=0; r_cnt < r_max; r_cnt++) {
rdftype = sldns_rr_descriptor_field_type(desc, r_cnt);
delimiters = rrinternal_get_delims(rdftype, r_cnt, r_max);
quoted = rrinternal_get_quoted(strbuf, &delimiters, rdftype);
rdata_len -= label_len;
rdata += label_len;
}
/* The root label is one more character, so smaller
* than 1 + 1 means no Svcparam Keys */
if (rdata_len < 2 || *rdata != 0)
return LDNS_WIREPARSE_ERR_OK;
/*
* trailing spaces are allowed
* leading spaces are not allowed
* allow ttl to be optional
* class is optional too
* if ttl is missing, and default_ttl is 0, use DEF_TTL
* allow ttl to be written as 1d3h
* So the RR should look like. e.g.
* miek.nl. 3600 IN MX 10 elektron.atoom.net
* or
* miek.nl. 1h IN MX 10 elektron.atoom.net
* or
* miek.nl. IN MX 10 elektron.atoom.net
*/
static int
sldns_str2wire_rr_buf_internal(const char* str, uint8_t* rr, size_t* len,
size_t* dname_len, uint32_t default_ttl, uint8_t* origin,
size_t origin_len, uint8_t* prev, size_t prev_len, int question)
{
int status;
int not_there = 0;
char token[LDNS_MAX_RDFLEN+1];
uint32_t ttl = 0;
uint16_t tp = 0, cl = 0;
size_t ddlen = 0;
/* Strip whitespace from the start and the end of <line>. */
char *
sldns_strip_ws(char *line)
{
char *s = line, *e;
for (s = line; *s && isspace((unsigned char)*s); s++)
;
for (e = strchr(s, 0); e > s+2 && isspace((unsigned char)e[-1]) && e[-2] != '\\'; e--)
;
*e = 0;
return s;
}
int sldns_fp2wire_rr_buf(FILE* in, uint8_t* rr, size_t* len, size_t* dname_len,
struct sldns_file_parse_state* parse_state)
{
char line[LDNS_RR_BUF_SIZE+1];
ssize_t size;
/* read an entire line in from the file */
if((size = sldns_fget_token_l(in, line, LDNS_PARSE_SKIP_SPACE,
LDNS_RR_BUF_SIZE, parse_state?&parse_state->lineno:NULL))
== -1) {
/* if last line was empty, we are now at feof, which is not
* always a parse error (happens when for instance last line
* was a comment)
*/
return LDNS_WIREPARSE_ERR_SYNTAX;
}
/* we can have the situation, where we've read ok, but still got
* no bytes to play with, in this case size is 0 */
if(size == 0) {
if(*len > 0)
rr[0] = 0;
*len = 0;
*dname_len = 0;
return LDNS_WIREPARSE_ERR_OK;
}
if (endptr > buf /* digits seen */
&& *endptr == 0 /* no non-digit chars after digits */
&& key_value <= 65535) /* no overflow */
return key_value;
} else switch (key_len) {
case 3:
if (!strncmp(key, "ech", key_len))
return SVCB_KEY_ECH;
break;
case 4:
if (!strncmp(key, "alpn", key_len))
return SVCB_KEY_ALPN;
if (!strncmp(key, "port", key_len))
return SVCB_KEY_PORT;
break;
case 7:
if (!strncmp(key, "dohpath", key_len))
return SVCB_KEY_DOHPATH;
break;
case 8:
if (!strncmp(key, "ipv4hint", key_len))
return SVCB_KEY_IPV4HINT;
if (!strncmp(key, "ipv6hint", key_len))
return SVCB_KEY_IPV6HINT;
break;
case 9:
if (!strncmp(key, "mandatory", key_len))
return SVCB_KEY_MANDATORY;
if (!strncmp(key, "echconfig", key_len))
return SVCB_KEY_ECH; /* allow "echconfig" as well as "ech" */
break;
case 15:
if (!strncmp(key, "no-default-alpn", key_len))
return SVCB_KEY_NO_DEFAULT_ALPN;
break;
default:
break;
}
/* Although the returned value might be used by the caller,
* the parser has erred, so the zone will not be loaded.
*/
return -1;
}
static int
sldns_str2wire_svcparam_port(const char* val, uint8_t* rd, size_t* rd_len)
{
unsigned long int port;
char *endptr;
if (*rd_len < 6)
return LDNS_WIREPARSE_ERR_BUFFER_TOO_SMALL;
port = strtoul(val, &endptr, 10);
if (endptr > val /* digits seen */
&& *endptr == 0 /* no non-digit chars after digits */
&& port <= 65535) { /* no overflow */
for (i = 0, count = 1; val[i]; i++) {
if (val[i] == ',')
count += 1;
if (count > SVCB_MAX_COMMA_SEPARATED_VALUES) {
return LDNS_WIREPARSE_ERR_SVCB_IPV4_TOO_MANY_ADDRESSES;
}
}
if (*rd_len < (LDNS_IP4ADDRLEN * count) + 4)
return LDNS_WIREPARSE_ERR_BUFFER_TOO_SMALL;
/* count is number of comma's in val + 1; so the actual number of IPv4
* addresses in val
*/
sldns_write_uint16(rd, SVCB_KEY_IPV4HINT);
sldns_write_uint16(rd + 2, LDNS_IP4ADDRLEN * count);
*rd_len = 4;
while (count) {
if (!(next_ip_str = strchr(val, ','))) {
if (inet_pton(AF_INET, val, rd + *rd_len) != 1)
break;
*rd_len += LDNS_IP4ADDRLEN;
assert(count == 1);
} else if (next_ip_str - val >= (int)sizeof(ip_str))
break;
for (i = 0, count = 1; val[i]; i++) {
if (val[i] == ',')
count += 1;
if (count > SVCB_MAX_COMMA_SEPARATED_VALUES) {
return LDNS_WIREPARSE_ERR_SVCB_IPV6_TOO_MANY_ADDRESSES;
}
}
if (*rd_len < (LDNS_IP6ADDRLEN * count) + 4)
return LDNS_WIREPARSE_ERR_BUFFER_TOO_SMALL;
/* count is number of comma's in val + 1; so the actual number of IPv6
* addresses in val
*/
sldns_write_uint16(rd, SVCB_KEY_IPV6HINT);
sldns_write_uint16(rd + 2, LDNS_IP6ADDRLEN * count);
*rd_len = 4;
while (count) {
if (!(next_ip_str = strchr(val, ','))) {
if (inet_pton(AF_INET6, val, rd + *rd_len) != 1)
break;
*rd_len += LDNS_IP6ADDRLEN;
assert(count == 1);
} else if (next_ip_str - val >= (int)sizeof(ip_str))
break;
val_len -= next_key - val + 1;
val = next_key + 1; /* skip the comma */
}
/* In draft-ietf-dnsop-svcb-https-06 Section 7:
*
* "In wire format, the keys are represented by their numeric
* values in network byte order, concatenated in ascending order."
*/
qsort((void *)(rd + 4), count, sizeof(uint16_t), sldns_network_uint16_cmp);
/* The code below revolves around semantic errors in the SVCParam set.
* So long as we do not distinguish between running Unbound as a primary
* or as a secondary, we default to secondary behavior and we ignore the
* semantic errors. */
#ifdef SVCB_SEMANTIC_ERRORS
/* In draft-ietf-dnsop-svcb-https-06 Section 8
* automatically mandatory MUST NOT appear in its own value-list
*/
if (sldns_read_uint16(rd + 4) == SVCB_KEY_MANDATORY)
return LDNS_WIREPARSE_ERR_SVCB_MANDATORY_IN_MANDATORY;
/* Guarantee key uniqueness. After the sort we only need to
* compare neighbouring keys */
if (count > 1) {
for (i = 0; i < count - 1; i++) {
uint8_t* current_pos = (rd + 4 + (sizeof(uint16_t) * i));
uint16_t key = sldns_read_uint16(current_pos);
static const char*
sldns_str2wire_svcbparam_parse_next_unescaped_comma(const char *val)
{
while (*val) {
/* Only return when the comma is not escaped*/
if (*val == '\\'){
++val;
if (!*val)
break;
} else if (*val == ',')
return val;
val++;
}
return NULL;
}
/* The source is already properly unescaped, this double unescaping is purely to allow for
* comma's in comma separated alpn lists.
*
* In draft-ietf-dnsop-svcb-https-06 Section 7:
* To enable simpler parsing, this SvcParamValue MUST NOT contain escape sequences.
*/
static size_t
sldns_str2wire_svcbparam_parse_copy_unescaped(uint8_t *dst,
const char *src, size_t len)
{
uint8_t *orig_dst = dst;
while (len) {
if (*src == '\\') {
src++;
len--;
if (!len)
break;
}
*dst++ = *src++;
len--;
}
return (size_t)(dst - orig_dst);
}
/* RFC6570#section-2.1
* "The characters outside of expressions in a URI Template string are
* intended to be copied literally"
* Practically this means we do not have to look for "double escapes"
* like in the alpn value list.
*/
val_len = strlen(val);
if (*rd_len < 4 + val_len) {
return LDNS_WIREPARSE_ERR_BUFFER_TOO_SMALL;
}
if (svcparamkey < 0) {
return LDNS_WIREPARSE_ERR_SVCB_UNKNOWN_KEY;
}
/* key without value */
if (val == NULL) {
switch (svcparamkey) {
#ifdef SVCB_SEMANTIC_ERRORS
case SVCB_KEY_MANDATORY:
case SVCB_KEY_ALPN:
case SVCB_KEY_PORT:
case SVCB_KEY_IPV4HINT:
case SVCB_KEY_IPV6HINT:
case SVCB_KEY_DOHPATH:
return LDNS_WIREPARSE_ERR_SVCB_MISSING_PARAM;
#endif
default:
if (*rd_len < 4)
return LDNS_WIREPARSE_ERR_BUFFER_TOO_SMALL;
sldns_write_uint16(rd, svcparamkey);
sldns_write_uint16(rd + 2, 0);
*rd_len = 4;
return LDNS_WIREPARSE_ERR_OK;
}
}
/* value is non-empty */
switch (svcparamkey) {
case SVCB_KEY_PORT:
return sldns_str2wire_svcparam_port(val, rd, rd_len);
case SVCB_KEY_IPV4HINT:
return sldns_str2wire_svcbparam_ipv4hint(val, rd, rd_len);
case SVCB_KEY_IPV6HINT:
return sldns_str2wire_svcbparam_ipv6hint(val, rd, rd_len);
case SVCB_KEY_MANDATORY:
return sldns_str2wire_svcbparam_mandatory(val, rd, rd_len);
#ifdef SVCB_SEMANTIC_ERRORS
case SVCB_KEY_NO_DEFAULT_ALPN:
return LDNS_WIREPARSE_ERR_SVCB_NO_DEFAULT_ALPN_VALUE;
#endif
case SVCB_KEY_ECH:
return sldns_str2wire_svcbparam_ech_value(val, rd, rd_len);
case SVCB_KEY_ALPN:
return sldns_str2wire_svcbparam_alpn_value(val, rd, rd_len);
case SVCB_KEY_DOHPATH:
return sldns_str2wire_svcbparam_dohpath_value(val, rd, rd_len);
default:
str_len = strlen(val);
if (*rd_len < 4 + str_len)
return LDNS_WIREPARSE_ERR_BUFFER_TOO_SMALL;
sldns_write_uint16(rd, svcparamkey);
sldns_write_uint16(rd + 2, str_len);
memcpy(rd + 4, val, str_len);
*rd_len = 4 + str_len;
/* need ip addr and only ip addr for inet_pton */
ip_str_len = (size_t) (strchr(my_str, '/') - my_str);
if(ip_str_len+1 > sizeof(my_ip_str))
return LDNS_WIREPARSE_ERR_INVALID_STR;
(void)strlcpy(my_ip_str, my_str, sizeof(my_ip_str));
my_ip_str[ip_str_len] = 0;
if (family == 1) {
/* ipv4 */
if(inet_pton(AF_INET, my_ip_str, data+4) == 0)
return LDNS_WIREPARSE_ERR_INVALID_STR;
for (i = 0; i < 4; i++) {
if (data[i+4] != 0) {
adflength = i + 1;
}
}
} else if (family == 2) {
/* ipv6 */
if (inet_pton(AF_INET6, my_ip_str, data+4) == 0)
return LDNS_WIREPARSE_ERR_INVALID_STR;
for (i = 0; i < 16; i++) {
if (data[i+4] != 0) {
adflength = i + 1;
}
}
} else {
/* unknown family */
return LDNS_WIREPARSE_ERR_INVALID_STR;
}
/* An certificate alg field can either be specified as a 8 bits number
* or by its symbolic name. Handle both */
int sldns_str2wire_cert_alg_buf(const char* str, uint8_t* rd, size_t* len)
{
sldns_lookup_table *lt = sldns_lookup_by_name(sldns_cert_algorithms,
str);
if(*len < 2)
return LDNS_WIREPARSE_ERR_BUFFER_TOO_SMALL;
if(lt) {
sldns_write_uint16(rd, (uint16_t)lt->id);
} else {
int s = sldns_str2wire_int16_buf(str, rd, len);
if(s) return s;
if(sldns_read_uint16(rd) == 0)
return LDNS_WIREPARSE_ERR_CERT_BAD_ALGORITHM;
}
*len = 2;
return LDNS_WIREPARSE_ERR_OK;
}
/* An alg field can either be specified as a 8 bits number
* or by its symbolic name. Handle both */
int sldns_str2wire_alg_buf(const char* str, uint8_t* rd, size_t* len)
{
sldns_lookup_table *lt = sldns_lookup_by_name(sldns_algorithms, str);
if(*len < 1)
return LDNS_WIREPARSE_ERR_BUFFER_TOO_SMALL;
if(lt) {
rd[0] = (uint8_t)lt->id;
*len = 1;
} else {
/* try as-is (a number) */
return sldns_str2wire_int8_buf(str, rd, len);
}
return LDNS_WIREPARSE_ERR_OK;
}
/* store number */
s = 1000.0 * s;
/* add a little to make floor in conversion a round */
s += 0.0005;
latitude = (uint32_t) s;
latitude += 1000 * 60 * m;
latitude += 1000 * 60 * 60 * h;
if (northerness) {
latitude = equator + latitude;
} else {
latitude = equator - latitude;
}
while (isblank((unsigned char)*my_str)) {
my_str++;
}
if (isdigit((unsigned char) *my_str)) {
h = (uint32_t) strtol(my_str, &my_str, 10);
} else {
return LDNS_WIREPARSE_ERR_INVALID_STR;
}
while (isblank((unsigned char) *my_str)) {
my_str++;
}
if (isdigit((unsigned char) *my_str)) {
m = (uint32_t) strtol(my_str, &my_str, 10);
} else if (*my_str == 'E' || *my_str == 'W') {
goto east;
} else {
return LDNS_WIREPARSE_ERR_INVALID_STR;
}
while (isblank((unsigned char)*my_str)) {
my_str++;
}
if (isdigit((unsigned char) *my_str)) {
s = strtod(my_str, &my_str);
}
/* skip blanks before easterness */
while (isblank((unsigned char)*my_str)) {
my_str++;
}
/* store number */
s *= 1000.0;
/* add a little to make floor in conversion a round */
s += 0.0005;
longitude = (uint32_t) s;
longitude += 1000 * 60 * m;
longitude += 1000 * 60 * 60 * h;