mirror of https://github.com/getdnsapi/getdns.git
Update gldns
This commit is contained in:
parent
f97ee14b69
commit
b181782e0e
|
@ -13,7 +13,7 @@
|
|||
#include "gldns/gbuffer.h"
|
||||
|
||||
#include <limits.h>
|
||||
#include <stdlib.h>
|
||||
#include <strings.h>
|
||||
|
||||
gldns_lookup_table gldns_directive_types[] = {
|
||||
{ GLDNS_DIR_TTL, "$TTL" },
|
||||
|
@ -34,7 +34,7 @@ gldns_fget_token_l(FILE *f, char *token, const char *delim, size_t limit, int *l
|
|||
{
|
||||
int c, prev_c;
|
||||
int p; /* 0 -> no parentheses seen, >0 nr of ( seen */
|
||||
int com, quoted;
|
||||
int com, quoted, only_blank;
|
||||
char *t;
|
||||
size_t i;
|
||||
const char *d;
|
||||
|
@ -53,6 +53,7 @@ gldns_fget_token_l(FILE *f, char *token, const char *delim, size_t limit, int *l
|
|||
com = 0;
|
||||
quoted = 0;
|
||||
prev_c = 0;
|
||||
only_blank = 1; /* Assume we got only <blank> until now */
|
||||
t = token;
|
||||
if (del[0] == '"') {
|
||||
quoted = 1;
|
||||
|
@ -101,6 +102,22 @@ gldns_fget_token_l(FILE *f, char *token, const char *delim, size_t limit, int *l
|
|||
if (line_nr) {
|
||||
*line_nr = *line_nr + 1;
|
||||
}
|
||||
if (only_blank && i > 0) {
|
||||
/* Got only <blank> so far. Reset and try
|
||||
* again with the next line.
|
||||
*/
|
||||
i = 0;
|
||||
t = token;
|
||||
}
|
||||
if (p == 0) {
|
||||
/* If p != 0 then the next line is a continuation. So
|
||||
* we assume that the next line starts with a blank only
|
||||
* if it is actually a new line.
|
||||
*/
|
||||
only_blank = 1; /* Assume next line starts with
|
||||
* <blank>.
|
||||
*/
|
||||
}
|
||||
if (p == 0 && i > 0) {
|
||||
goto tokenread;
|
||||
} else {
|
||||
|
@ -131,12 +148,29 @@ gldns_fget_token_l(FILE *f, char *token, const char *delim, size_t limit, int *l
|
|||
|
||||
/* check if we hit the delim */
|
||||
for (d = del; *d; d++) {
|
||||
if (c == *d)
|
||||
break;
|
||||
}
|
||||
|
||||
if (c == *d && i > 0 && prev_c != '\\' && p == 0) {
|
||||
if (c == '\n' && line_nr) {
|
||||
*line_nr = *line_nr + 1;
|
||||
}
|
||||
if (only_blank) {
|
||||
/* Got only <blank> so far. Reset and
|
||||
* try again with the next line.
|
||||
*/
|
||||
i = 0;
|
||||
t = token;
|
||||
only_blank = 1;
|
||||
prev_c = c;
|
||||
continue;
|
||||
}
|
||||
goto tokenread;
|
||||
}
|
||||
if (c != ' ' && c != '\t') {
|
||||
/* Found something that is not <blank> */
|
||||
only_blank= 0;
|
||||
}
|
||||
if (c != '\0' && c != '\n') {
|
||||
i++;
|
||||
|
@ -149,9 +183,14 @@ gldns_fget_token_l(FILE *f, char *token, const char *delim, size_t limit, int *l
|
|||
if (c != '\0' && c != '\n') {
|
||||
*t++ = c;
|
||||
}
|
||||
if (c == '\n' && line_nr) {
|
||||
if (c == '\n') {
|
||||
if (line_nr) {
|
||||
*line_nr = *line_nr + 1;
|
||||
}
|
||||
only_blank = 1; /* Assume next line starts with
|
||||
* <blank>.
|
||||
*/
|
||||
}
|
||||
if (c == '\\' && prev_c == '\\')
|
||||
prev_c = 0;
|
||||
else prev_c = c;
|
||||
|
|
|
@ -14,12 +14,8 @@
|
|||
|
||||
#include "config.h"
|
||||
#include "gldns/parseutil.h"
|
||||
#ifdef HAVE_SYS_TIME_H
|
||||
#include <sys/time.h>
|
||||
#endif
|
||||
#ifdef HAVE_TIME_H
|
||||
#include <time.h>
|
||||
#endif
|
||||
#include <ctype.h>
|
||||
|
||||
gldns_lookup_table *
|
||||
|
@ -213,11 +209,13 @@ gldns_hexdigit_to_int(char ch)
|
|||
}
|
||||
|
||||
uint32_t
|
||||
gldns_str2period(const char *nptr, const char **endptr)
|
||||
gldns_str2period(const char *nptr, const char **endptr, int* overflow)
|
||||
{
|
||||
int sign = 0;
|
||||
uint32_t i = 0;
|
||||
uint32_t seconds = 0;
|
||||
const uint32_t maxint = 0xffffffff;
|
||||
*overflow = 0;
|
||||
|
||||
for(*endptr = nptr; **endptr; (*endptr)++) {
|
||||
switch (**endptr) {
|
||||
|
@ -240,26 +238,46 @@ gldns_str2period(const char *nptr, const char **endptr)
|
|||
break;
|
||||
case 's':
|
||||
case 'S':
|
||||
if(seconds > maxint-i) {
|
||||
*overflow = 1;
|
||||
return 0;
|
||||
}
|
||||
seconds += i;
|
||||
i = 0;
|
||||
break;
|
||||
case 'm':
|
||||
case 'M':
|
||||
if(i > maxint/60 || seconds > maxint-(i*60)) {
|
||||
*overflow = 1;
|
||||
return 0;
|
||||
}
|
||||
seconds += i * 60;
|
||||
i = 0;
|
||||
break;
|
||||
case 'h':
|
||||
case 'H':
|
||||
if(i > maxint/(60*60) || seconds > maxint-(i*60*60)) {
|
||||
*overflow = 1;
|
||||
return 0;
|
||||
}
|
||||
seconds += i * 60 * 60;
|
||||
i = 0;
|
||||
break;
|
||||
case 'd':
|
||||
case 'D':
|
||||
if(i > maxint/(60*60*24) || seconds > maxint-(i*60*60*24)) {
|
||||
*overflow = 1;
|
||||
return 0;
|
||||
}
|
||||
seconds += i * 60 * 60 * 24;
|
||||
i = 0;
|
||||
break;
|
||||
case 'w':
|
||||
case 'W':
|
||||
if(i > maxint/(60*60*24*7) || seconds > maxint-(i*60*60*24*7)) {
|
||||
*overflow = 1;
|
||||
return 0;
|
||||
}
|
||||
seconds += i * 60 * 60 * 24 * 7;
|
||||
i = 0;
|
||||
break;
|
||||
|
@ -273,15 +291,27 @@ gldns_str2period(const char *nptr, const char **endptr)
|
|||
case '7':
|
||||
case '8':
|
||||
case '9':
|
||||
if(i > maxint/10 || i*10 > maxint - (**endptr - '0')) {
|
||||
*overflow = 1;
|
||||
return 0;
|
||||
}
|
||||
i *= 10;
|
||||
i += (**endptr - '0');
|
||||
break;
|
||||
default:
|
||||
if(seconds > maxint-i) {
|
||||
*overflow = 1;
|
||||
return 0;
|
||||
}
|
||||
seconds += i;
|
||||
/* disregard signedness */
|
||||
return seconds;
|
||||
}
|
||||
}
|
||||
if(seconds > maxint-i) {
|
||||
*overflow = 1;
|
||||
return 0;
|
||||
}
|
||||
seconds += i;
|
||||
/* disregard signedness */
|
||||
return seconds;
|
||||
|
|
|
@ -74,9 +74,11 @@ struct tm * gldns_serial_arithmetics_gmtime_r(int32_t time, time_t now, struct t
|
|||
* converts a ttl value (like 5d2h) to a long.
|
||||
* \param[in] nptr the start of the string
|
||||
* \param[out] endptr points to the last char in case of error
|
||||
* \param[out] overflow returns if the string causes integer overflow error,
|
||||
* the number is too big, string of digits too long.
|
||||
* \return the convert duration value
|
||||
*/
|
||||
uint32_t gldns_str2period(const char *nptr, const char **endptr);
|
||||
uint32_t gldns_str2period(const char *nptr, const char **endptr, int* overflow);
|
||||
|
||||
/**
|
||||
* Returns the int value of the given (hex) digit
|
||||
|
|
|
@ -97,18 +97,22 @@ extern "C" {
|
|||
#define QDCOUNT(wirebuf) (ntohs(*(uint16_t *)(wirebuf+QDCOUNT_OFF)))
|
||||
*/
|
||||
#define GLDNS_QDCOUNT(wirebuf) (gldns_read_uint16(wirebuf+GLDNS_QDCOUNT_OFF))
|
||||
#define GLDNS_QDCOUNT_SET(wirebuf, i) (gldns_write_uint16(wirebuf+GLDNS_QDCOUNT_OFF, i))
|
||||
|
||||
/* Counter of the answer section */
|
||||
#define GLDNS_ANCOUNT_OFF 6
|
||||
#define GLDNS_ANCOUNT(wirebuf) (gldns_read_uint16(wirebuf+GLDNS_ANCOUNT_OFF))
|
||||
#define GLDNS_ANCOUNT_SET(wirebuf, i) (gldns_write_uint16(wirebuf+GLDNS_ANCOUNT_OFF, i))
|
||||
|
||||
/* Counter of the authority section */
|
||||
#define GLDNS_NSCOUNT_OFF 8
|
||||
#define GLDNS_NSCOUNT(wirebuf) (gldns_read_uint16(wirebuf+GLDNS_NSCOUNT_OFF))
|
||||
#define GLDNS_NSCOUNT_SET(wirebuf, i) (gldns_write_uint16(wirebuf+GLDNS_NSCOUNT_OFF, i))
|
||||
|
||||
/* Counter of the additional section */
|
||||
#define GLDNS_ARCOUNT_OFF 10
|
||||
#define GLDNS_ARCOUNT(wirebuf) (gldns_read_uint16(wirebuf+GLDNS_ARCOUNT_OFF))
|
||||
#define GLDNS_ARCOUNT_SET(wirebuf, i) (gldns_write_uint16(wirebuf+GLDNS_ARCOUNT_OFF, i))
|
||||
|
||||
/**
|
||||
* The sections of a packet
|
||||
|
|
|
@ -392,9 +392,9 @@ static gldns_rr_descriptor rdata_field_descriptors[] = {
|
|||
/* 63 */
|
||||
{GLDNS_RR_TYPE_ZONEMD, "ZONEMD", 4, 4, type_zonemd_wireformat, GLDNS_RDF_TYPE_NONE, GLDNS_RR_NO_COMPRESS, 0 },
|
||||
/* 64 */
|
||||
{GLDNS_RR_TYPE_SVCB, "SVCB", 2, 2, type_svcb_wireformat, GLDNS_RDF_TYPE_SVCPARAM, GLDNS_RR_NO_COMPRESS, 0 },
|
||||
{GLDNS_RR_TYPE_SVCB, "SVCB", 2, 2, type_svcb_wireformat, GLDNS_RDF_TYPE_SVCPARAM, GLDNS_RR_NO_COMPRESS, 1 },
|
||||
/* 65 */
|
||||
{GLDNS_RR_TYPE_HTTPS, "HTTPS", 2, 2, type_svcb_wireformat, GLDNS_RDF_TYPE_SVCPARAM, GLDNS_RR_NO_COMPRESS, 0 },
|
||||
{GLDNS_RR_TYPE_HTTPS, "HTTPS", 2, 2, type_svcb_wireformat, GLDNS_RDF_TYPE_SVCPARAM, GLDNS_RR_NO_COMPRESS, 1 },
|
||||
{(enum gldns_enum_rr_type)0, "TYPE66", 1, 1, type_0_wireformat, GLDNS_RDF_TYPE_NONE, GLDNS_RR_NO_COMPRESS, 0 },
|
||||
{(enum gldns_enum_rr_type)0, "TYPE67", 1, 1, type_0_wireformat, GLDNS_RDF_TYPE_NONE, GLDNS_RR_NO_COMPRESS, 0 },
|
||||
{(enum gldns_enum_rr_type)0, "TYPE68", 1, 1, type_0_wireformat, GLDNS_RDF_TYPE_NONE, GLDNS_RR_NO_COMPRESS, 0 },
|
||||
|
|
|
@ -440,10 +440,42 @@ enum gldns_enum_edns_option
|
|||
GLDNS_EDNS_CLIENT_SUBNET = 8, /* RFC7871 */
|
||||
GLDNS_EDNS_KEEPALIVE = 11, /* draft-ietf-dnsop-edns-tcp-keepalive*/
|
||||
GLDNS_EDNS_PADDING = 12, /* RFC7830 */
|
||||
GLDNS_EDNS_EDE = 15, /* RFC8914 */
|
||||
GLDNS_EDNS_CLIENT_TAG = 16 /* draft-bellis-dnsop-edns-tags-01 */
|
||||
};
|
||||
typedef enum gldns_enum_edns_option gldns_edns_option;
|
||||
|
||||
enum gldns_enum_ede_code
|
||||
{
|
||||
GLDNS_EDE_NONE = -1, /* EDE undefined for internal use */
|
||||
GLDNS_EDE_OTHER = 0,
|
||||
GLDNS_EDE_UNSUPPORTED_DNSKEY_ALG = 1,
|
||||
GLDNS_EDE_UNSUPPORTED_DS_DIGEST = 2,
|
||||
GLDNS_EDE_STALE_ANSWER = 3,
|
||||
GLDNS_EDE_FORGED_ANSWER = 4,
|
||||
GLDNS_EDE_DNSSEC_INDETERMINATE = 5,
|
||||
GLDNS_EDE_DNSSEC_BOGUS = 6,
|
||||
GLDNS_EDE_SIGNATURE_EXPIRED = 7,
|
||||
GLDNS_EDE_SIGNATURE_NOT_YET_VALID = 8,
|
||||
GLDNS_EDE_DNSKEY_MISSING = 9,
|
||||
GLDNS_EDE_RRSIGS_MISSING = 10,
|
||||
GLDNS_EDE_NO_ZONE_KEY_BIT_SET = 11,
|
||||
GLDNS_EDE_NSEC_MISSING = 12,
|
||||
GLDNS_EDE_CACHED_ERROR = 13,
|
||||
GLDNS_EDE_NOT_READY = 14,
|
||||
GLDNS_EDE_BLOCKED = 15,
|
||||
GLDNS_EDE_CENSORED = 16,
|
||||
GLDNS_EDE_FILTERED = 17,
|
||||
GLDNS_EDE_PROHIBITED = 18,
|
||||
GLDNS_EDE_STALE_NXDOMAIN_ANSWER = 19,
|
||||
GLDNS_EDE_NOT_AUTHORITATIVE = 20,
|
||||
GLDNS_EDE_NOT_SUPPORTED = 21,
|
||||
GLDNS_EDE_NO_REACHABLE_AUTHORITY = 22,
|
||||
GLDNS_EDE_NETWORK_ERROR = 23,
|
||||
GLDNS_EDE_INVALID_DATA = 24,
|
||||
};
|
||||
typedef enum gldns_enum_ede_code gldns_ede_code;
|
||||
|
||||
#define GLDNS_EDNS_MASK_DO_BIT 0x8000
|
||||
|
||||
/** TSIG and TKEY extended rcodes (16bit), 0-15 are the normal rcodes. */
|
||||
|
|
|
@ -250,11 +250,16 @@ rrinternal_get_ttl(gldns_buffer* strbuf, char* token, size_t token_len,
|
|||
int* not_there, uint32_t* ttl, uint32_t default_ttl)
|
||||
{
|
||||
const char* endptr;
|
||||
int overflow;
|
||||
if(gldns_bget_token(strbuf, token, "\t\n ", token_len) == -1) {
|
||||
return RET_ERR(GLDNS_WIREPARSE_ERR_SYNTAX_TTL,
|
||||
gldns_buffer_position(strbuf));
|
||||
}
|
||||
*ttl = (uint32_t) gldns_str2period(token, &endptr);
|
||||
*ttl = (uint32_t) gldns_str2period(token, &endptr, &overflow);
|
||||
if(overflow) {
|
||||
return RET_ERR(GLDNS_WIREPARSE_ERR_SYNTAX_INTEGER_OVERFLOW,
|
||||
gldns_buffer_position(strbuf));
|
||||
}
|
||||
|
||||
if (strlen(token) > 0 && !isdigit((unsigned char)token[0])) {
|
||||
*not_there = 1;
|
||||
|
@ -374,7 +379,8 @@ rrinternal_get_quoted(gldns_buffer* strbuf, const char** delimiters,
|
|||
|
||||
/* skip spaces */
|
||||
while(gldns_buffer_remaining(strbuf) > 0 &&
|
||||
*(gldns_buffer_current(strbuf)) == ' ') {
|
||||
(*(gldns_buffer_current(strbuf)) == ' ' ||
|
||||
*(gldns_buffer_current(strbuf)) == '\t')) {
|
||||
gldns_buffer_skip(strbuf, 1);
|
||||
}
|
||||
|
||||
|
@ -607,7 +613,7 @@ gldns_affix_token(gldns_buffer* strbuf, char* token, size_t* token_len,
|
|||
/* 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
|
||||
* assignement below. */
|
||||
* assignment below. */
|
||||
if(addlen < 2) return 0;
|
||||
token[*token_strlen] = ' ';
|
||||
token[++(*token_strlen)] = 0;
|
||||
|
@ -671,10 +677,10 @@ static int gldns_str2wire_check_svcbparams(uint8_t* rdata, uint16_t rdata_len)
|
|||
,gldns_str2wire_svcparam_key_cmp);
|
||||
|
||||
|
||||
/* The code below revolves around sematic errors in the SVCParam set.
|
||||
/* 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
|
||||
* sematic errors. */
|
||||
* semantic errors. */
|
||||
|
||||
#ifdef SVCB_SEMANTIC_ERRORS
|
||||
{
|
||||
|
@ -776,7 +782,8 @@ rrinternal_parse_rdata(gldns_buffer* strbuf, char* token, size_t token_len,
|
|||
|
||||
/* unknown RR data */
|
||||
if(token_strlen>=2 && strncmp(token, "\\#", 2) == 0 &&
|
||||
!quoted && (token_strlen == 2 || token[2]==' ')) {
|
||||
!quoted && (token_strlen == 2 || token[2]==' ' ||
|
||||
token[2]=='\t')) {
|
||||
was_unknown_rr_format = 1;
|
||||
if((status=rrinternal_parse_unknown(strbuf, token,
|
||||
token_len, rr, rr_len, &rr_cur_len,
|
||||
|
@ -894,7 +901,7 @@ gldns_str2wire_rr_buf_internal(const char* str, uint8_t* rr, size_t* len,
|
|||
{
|
||||
int status;
|
||||
int not_there = 0;
|
||||
char token[GLDNS_MAX_RDFLEN+1] = "";
|
||||
char token[GLDNS_MAX_RDFLEN+1];
|
||||
uint32_t ttl = 0;
|
||||
uint16_t tp = 0, cl = 0;
|
||||
size_t ddlen = 0;
|
||||
|
@ -1056,12 +1063,15 @@ int gldns_fp2wire_rr_buf(FILE* in, uint8_t* rr, size_t* len, size_t* dname_len,
|
|||
return s;
|
||||
} else if(strncmp(line, "$TTL", 4) == 0 && isspace((unsigned char)line[4])) {
|
||||
const char* end = NULL;
|
||||
int overflow = 0;
|
||||
strlcpy((char*)rr, line, *len);
|
||||
*len = 0;
|
||||
*dname_len = 0;
|
||||
if(!parse_state) return GLDNS_WIREPARSE_ERR_OK;
|
||||
parse_state->default_ttl = gldns_str2period(
|
||||
gldns_strip_ws(line+5), &end);
|
||||
gldns_strip_ws(line+5), &end, &overflow);
|
||||
if(overflow)
|
||||
return GLDNS_WIREPARSE_ERR_SYNTAX_INTEGER_OVERFLOW;
|
||||
} else if (strncmp(line, "$INCLUDE", 8) == 0) {
|
||||
strlcpy((char*)rr, line, *len);
|
||||
*len = 0;
|
||||
|
@ -1118,7 +1128,7 @@ gldns_str2wire_svcparam_key_lookup(const char *key, size_t key_len)
|
|||
if (!strncmp(key, "mandatory", sizeof("mandatory")-1))
|
||||
return SVCB_KEY_MANDATORY;
|
||||
if (!strncmp(key, "echconfig", sizeof("echconfig")-1))
|
||||
return SVCB_KEY_ECH; /* allow "echconfig as well as "ech" */
|
||||
return SVCB_KEY_ECH; /* allow "echconfig" as well as "ech" */
|
||||
break;
|
||||
|
||||
case sizeof("alpn")-1:
|
||||
|
@ -1357,7 +1367,7 @@ gldns_str2wire_svcbparam_mandatory(const char* val, uint8_t* rd, size_t* rd_len)
|
|||
*/
|
||||
qsort((void *)(rd + 4), count, sizeof(uint16_t), gldns_network_uint16_cmp);
|
||||
|
||||
/* The code below revolves around sematic errors in the SVCParam set.
|
||||
/* 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. */
|
||||
|
@ -1589,12 +1599,12 @@ static int gldns_str2wire_svcparam_buf(const char* str, uint8_t* rd, size_t* rd_
|
|||
if (*val_in == '"') {
|
||||
val_in++;
|
||||
while (*val_in != '"'
|
||||
&& (unsigned)(val_out - unescaped_val + 1) < sizeof(unescaped_val)
|
||||
&& (size_t)(val_out - unescaped_val + 1) < sizeof(unescaped_val)
|
||||
&& gldns_parse_char( (uint8_t*) val_out, &val_in)) {
|
||||
val_out++;
|
||||
}
|
||||
} else {
|
||||
while ((unsigned)(val_out - unescaped_val + 1) < sizeof(unescaped_val)
|
||||
while ((size_t)(val_out - unescaped_val + 1) < sizeof(unescaped_val)
|
||||
&& gldns_parse_char( (uint8_t*) val_out, &val_in)) {
|
||||
val_out++;
|
||||
}
|
||||
|
@ -2160,9 +2170,13 @@ int gldns_str2wire_tsigtime_buf(const char* str, uint8_t* rd, size_t* len)
|
|||
int gldns_str2wire_period_buf(const char* str, uint8_t* rd, size_t* len)
|
||||
{
|
||||
const char* end;
|
||||
uint32_t p = gldns_str2period(str, &end);
|
||||
int overflow;
|
||||
uint32_t p = gldns_str2period(str, &end, &overflow);
|
||||
if(*end != 0)
|
||||
return RET_ERR(GLDNS_WIREPARSE_ERR_SYNTAX_PERIOD, end-str);
|
||||
if(overflow)
|
||||
return RET_ERR(GLDNS_WIREPARSE_ERR_SYNTAX_INTEGER_OVERFLOW,
|
||||
end-str);
|
||||
if(*len < 4)
|
||||
return GLDNS_WIREPARSE_ERR_BUFFER_TOO_SMALL;
|
||||
gldns_write_uint32(rd, p);
|
||||
|
|
|
@ -196,6 +196,7 @@ static gldns_lookup_table gldns_edns_options_data[] = {
|
|||
{ 8, "edns-client-subnet" },
|
||||
{ 11, "edns-tcp-keepalive"},
|
||||
{ 12, "Padding" },
|
||||
{ 15, "EDE"},
|
||||
{ 0, NULL}
|
||||
};
|
||||
gldns_lookup_table* gldns_edns_options = gldns_edns_options_data;
|
||||
|
@ -1072,7 +1073,7 @@ static int gldns_wire2str_svcparam_mandatory2str(char** s,
|
|||
assert(data_len > 0);
|
||||
|
||||
if (data_len % sizeof(uint16_t))
|
||||
return -1; // wireformat error, data_len must be multiple of shorts
|
||||
return -1; /* wireformat error, data_len must be multiple of shorts */
|
||||
w += gldns_str_print(s, slen, "=");
|
||||
w += gldns_print_svcparamkey(s, slen, gldns_read_uint16(data));
|
||||
data += 2;
|
||||
|
|
Loading…
Reference in New Issue