Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
W
wine-winehq
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
wine
wine-winehq
Commits
aec19e86
Commit
aec19e86
authored
Nov 02, 2021
by
Alexandre Julliard
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
dnsapi: Implement DnsExtractRecordsFromMessage().
Signed-off-by:
Alexandre Julliard
<
julliard@winehq.org
>
parent
87f94202
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
632 additions
and
26 deletions
+632
-26
main.c
dlls/dnsapi/main.c
+0
-26
record.c
dlls/dnsapi/record.c
+433
-0
record.c
dlls/dnsapi/tests/record.c
+199
-0
No files found.
dlls/dnsapi/main.c
View file @
aec19e86
...
...
@@ -151,32 +151,6 @@ VOID WINAPI DnsReleaseContextHandle( HANDLE context )
}
/******************************************************************************
* DnsExtractRecordsFromMessage_UTF8 [DNSAPI.@]
*
*/
DNS_STATUS
WINAPI
DnsExtractRecordsFromMessage_UTF8
(
PDNS_MESSAGE_BUFFER
buffer
,
WORD
len
,
PDNS_RECORDA
*
record
)
{
FIXME
(
"(%p,%d,%p) stub
\n
"
,
buffer
,
len
,
record
);
*
record
=
NULL
;
return
ERROR_SUCCESS
;
}
/******************************************************************************
* DnsExtractRecordsFromMessage_W [DNSAPI.@]
*
*/
DNS_STATUS
WINAPI
DnsExtractRecordsFromMessage_W
(
PDNS_MESSAGE_BUFFER
buffer
,
WORD
len
,
PDNS_RECORDW
*
record
)
{
FIXME
(
"(%p,%d,%p) stub
\n
"
,
buffer
,
len
,
record
);
*
record
=
NULL
;
return
ERROR_SUCCESS
;
}
/******************************************************************************
* DnsModifyRecordsInSet_A [DNSAPI.@]
*
*/
...
...
dlls/dnsapi/record.c
View file @
aec19e86
...
...
@@ -2,6 +2,7 @@
* DNS support
*
* Copyright (C) 2006 Hans Leidekker
* Copyright (C) 2021 Alexandre Julliard
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
...
...
@@ -95,6 +96,18 @@ const char *debugstr_type( unsigned short type )
}
}
static
const
char
*
debugstr_section
(
DNS_SECTION
section
)
{
switch
(
section
)
{
case
DnsSectionQuestion
:
return
"Question"
;
case
DnsSectionAnswer
:
return
"Answer"
;
case
DnsSectionAuthority
:
return
"Authority"
;
case
DnsSectionAddtional
:
return
"Additional"
;
default:
return
"??"
;
}
}
static
int
strcmpX
(
LPCVOID
str1
,
LPCVOID
str2
,
BOOL
wide
)
{
if
(
wide
)
...
...
@@ -103,6 +116,95 @@ static int strcmpX( LPCVOID str1, LPCVOID str2, BOOL wide )
return
lstrcmpiA
(
str1
,
str2
);
}
static
WORD
get_word
(
const
BYTE
**
ptr
)
{
WORD
ret
=
((
*
ptr
)[
0
]
<<
8
)
+
(
*
ptr
)[
1
];
*
ptr
+=
sizeof
(
WORD
);
return
ret
;
}
static
DWORD
get_dword
(
const
BYTE
**
ptr
)
{
DWORD
ret
=
((
*
ptr
)[
0
]
<<
24
)
+
((
*
ptr
)[
1
]
<<
16
)
+
((
*
ptr
)[
2
]
<<
8
)
+
(
*
ptr
)[
3
];
*
ptr
+=
sizeof
(
DWORD
);
return
ret
;
}
static
const
BYTE
*
skip_name
(
const
BYTE
*
ptr
,
const
BYTE
*
end
)
{
BYTE
len
;
while
(
ptr
<
end
&&
(
len
=
*
ptr
++
))
{
switch
(
len
&
0xc0
)
{
case
0
:
ptr
+=
len
;
continue
;
case
0xc0
:
ptr
++
;
break
;
default:
return
NULL
;
}
break
;
}
if
(
ptr
<
end
)
return
ptr
;
return
NULL
;
}
static
const
BYTE
*
skip_record
(
const
BYTE
*
ptr
,
const
BYTE
*
end
,
DNS_SECTION
section
)
{
WORD
len
;
if
(
!
(
ptr
=
skip_name
(
ptr
,
end
)))
return
NULL
;
ptr
+=
2
;
/* type */
ptr
+=
2
;
/* class */
if
(
section
!=
DnsSectionQuestion
)
{
ptr
+=
4
;
/* ttl */
if
(
ptr
+
2
>
end
)
return
NULL
;
len
=
get_word
(
&
ptr
);
ptr
+=
len
;
}
if
(
ptr
>
end
)
return
NULL
;
return
ptr
;
}
static
const
BYTE
*
get_name
(
const
BYTE
*
base
,
const
BYTE
*
end
,
const
BYTE
*
ptr
,
char
name
[
DNS_MAX_NAME_BUFFER_LENGTH
]
)
{
BYTE
len
;
char
*
out
=
name
;
const
BYTE
*
next
=
NULL
;
int
loop
=
0
;
while
(
ptr
<
end
&&
(
len
=
*
ptr
++
))
{
switch
(
len
&
0xc0
)
{
case
0
:
if
(
out
+
len
+
1
>=
name
+
DNS_MAX_NAME_BUFFER_LENGTH
)
return
NULL
;
if
(
out
>
name
)
*
out
++
=
'.'
;
memcpy
(
out
,
ptr
,
len
);
out
+=
len
;
ptr
+=
len
;
continue
;
case
0xc0
:
if
(
!
next
)
next
=
ptr
+
1
;
if
(
++
loop
>=
end
-
base
)
return
NULL
;
if
(
ptr
<
end
)
ptr
=
base
+
((
len
&
0x3f
)
<<
8
)
+
*
ptr
;
break
;
default:
return
NULL
;
}
}
if
(
ptr
>=
end
)
return
NULL
;
if
(
out
==
name
)
*
out
++
=
'.'
;
*
out
=
0
;
return
next
?
next
:
ptr
;
}
/******************************************************************************
* DnsRecordCompare [DNSAPI.@]
*
...
...
@@ -786,3 +888,334 @@ PDNS_RECORD WINAPI DnsRecordSetDetach( PDNS_RECORD set )
}
return
NULL
;
}
static
unsigned
int
get_record_size
(
WORD
type
,
const
BYTE
*
data
,
WORD
len
)
{
switch
(
type
)
{
case
DNS_TYPE_KEY
:
return
offsetof
(
DNS_RECORDA
,
Data
.
Key
.
Key
[
len
]
);
case
DNS_TYPE_SIG
:
return
offsetof
(
DNS_RECORDA
,
Data
.
Sig
.
Signature
[
len
]
);
case
DNS_TYPE_HINFO
:
case
DNS_TYPE_ISDN
:
case
DNS_TYPE_TEXT
:
case
DNS_TYPE_X25
:
{
int
i
;
const
BYTE
*
pos
=
data
;
for
(
i
=
0
;
pos
<
data
+
len
;
i
++
)
pos
+=
pos
[
0
]
+
1
;
return
offsetof
(
DNS_RECORDA
,
Data
.
Txt
.
pStringArray
[
i
]
);
}
case
DNS_TYPE_NULL
:
case
DNS_TYPE_OPT
:
return
offsetof
(
DNS_RECORDA
,
Data
.
Null
.
Data
[
len
]
);
case
DNS_TYPE_WKS
:
return
offsetof
(
DNS_RECORDA
,
Data
.
Wks
.
BitMask
[
len
/
8
]
);
case
DNS_TYPE_NXT
:
return
offsetof
(
DNS_RECORDA
,
Data
.
Nxt
.
wTypes
[
len
*
8
]
);
case
DNS_TYPE_WINS
:
return
offsetof
(
DNS_RECORDA
,
Data
.
Wins
.
WinsServers
[
len
/
4
]
);
default:
return
sizeof
(
DNS_RECORDA
);
}
}
static
DNS_STATUS
extract_rdata
(
const
BYTE
*
base
,
const
BYTE
*
end
,
const
BYTE
*
pos
,
WORD
len
,
WORD
type
,
DNS_RECORDA
*
r
)
{
DNS_CHARSET
in
=
DnsCharSetUtf8
,
out
=
r
->
Flags
.
S
.
CharSet
;
char
name
[
DNS_MAX_NAME_BUFFER_LENGTH
];
DNS_STATUS
ret
=
ERROR_SUCCESS
;
const
BYTE
*
rrend
=
pos
+
len
;
unsigned
int
i
;
switch
(
type
)
{
case
DNS_TYPE_A
:
if
(
pos
+
sizeof
(
DWORD
)
>
rrend
)
return
DNS_ERROR_BAD_PACKET
;
r
->
Data
.
A
.
IpAddress
=
*
(
const
DWORD
*
)
pos
;
r
->
wDataLength
=
sizeof
(
DNS_A_DATA
);
break
;
case
DNS_TYPE_AAAA
:
if
(
pos
+
sizeof
(
IP6_ADDRESS
)
>
rrend
)
return
DNS_ERROR_BAD_PACKET
;
for
(
i
=
0
;
i
<
sizeof
(
IP6_ADDRESS
)
/
sizeof
(
DWORD
);
i
++
)
{
r
->
Data
.
AAAA
.
Ip6Address
.
IP6Dword
[
i
]
=
*
(
const
DWORD
*
)
pos
;
pos
+=
sizeof
(
DWORD
);
}
r
->
wDataLength
=
sizeof
(
DNS_AAAA_DATA
);
break
;
case
DNS_TYPE_KEY
:
if
(
pos
+
4
>
rrend
)
return
DNS_ERROR_BAD_PACKET
;
r
->
Data
.
KEY
.
wFlags
=
get_word
(
&
pos
);
r
->
Data
.
KEY
.
chProtocol
=
*
pos
++
;
r
->
Data
.
KEY
.
chAlgorithm
=
*
pos
++
;
r
->
Data
.
KEY
.
wKeyLength
=
rrend
-
pos
;
memcpy
(
r
->
Data
.
KEY
.
Key
,
pos
,
r
->
Data
.
KEY
.
wKeyLength
);
r
->
wDataLength
=
offsetof
(
DNS_KEY_DATA
,
Key
[
r
->
Data
.
KEY
.
wKeyLength
]
);
break
;
case
DNS_TYPE_RP
:
case
DNS_TYPE_MINFO
:
if
(
!
(
pos
=
get_name
(
base
,
end
,
pos
,
name
)))
return
DNS_ERROR_BAD_PACKET
;
if
(
!
(
r
->
Data
.
MINFO
.
pNameMailbox
=
strcpyX
(
name
,
in
,
out
)))
return
ERROR_NOT_ENOUGH_MEMORY
;
if
(
!
get_name
(
base
,
end
,
pos
,
name
))
return
DNS_ERROR_BAD_PACKET
;
if
(
!
(
r
->
Data
.
MINFO
.
pNameErrorsMailbox
=
strcpyX
(
name
,
in
,
out
)))
{
heap_free
(
r
->
Data
.
MINFO
.
pNameMailbox
);
return
ERROR_NOT_ENOUGH_MEMORY
;
}
r
->
wDataLength
=
sizeof
(
DNS_MINFO_DATAA
);
break
;
case
DNS_TYPE_AFSDB
:
case
DNS_TYPE_RT
:
case
DNS_TYPE_MX
:
if
(
pos
+
sizeof
(
WORD
)
>
rrend
)
return
DNS_ERROR_BAD_PACKET
;
r
->
Data
.
MX
.
wPreference
=
get_word
(
&
pos
);
if
(
!
get_name
(
base
,
end
,
pos
,
name
))
return
DNS_ERROR_BAD_PACKET
;
if
(
!
(
r
->
Data
.
MX
.
pNameExchange
=
strcpyX
(
name
,
in
,
out
)))
return
ERROR_NOT_ENOUGH_MEMORY
;
r
->
wDataLength
=
sizeof
(
DNS_MX_DATAA
)
+
sizeof
(
DWORD
);
break
;
case
DNS_TYPE_NULL
:
r
->
Data
.
Null
.
dwByteCount
=
len
;
memcpy
(
r
->
Data
.
Null
.
Data
,
pos
,
len
);
r
->
wDataLength
=
offsetof
(
DNS_NULL_DATA
,
Data
[
len
]
);
break
;
case
DNS_TYPE_OPT
:
r
->
Data
.
OPT
.
wDataLength
=
len
;
r
->
Data
.
OPT
.
wPad
=
0
;
memcpy
(
r
->
Data
.
OPT
.
Data
,
pos
,
len
);
r
->
wDataLength
=
offsetof
(
DNS_OPT_DATA
,
Data
[
len
]
);
break
;
case
DNS_TYPE_CNAME
:
case
DNS_TYPE_NS
:
case
DNS_TYPE_MB
:
case
DNS_TYPE_MD
:
case
DNS_TYPE_MF
:
case
DNS_TYPE_MG
:
case
DNS_TYPE_MR
:
case
DNS_TYPE_PTR
:
if
(
!
get_name
(
base
,
end
,
pos
,
name
))
return
DNS_ERROR_BAD_PACKET
;
if
(
!
(
r
->
Data
.
PTR
.
pNameHost
=
strcpyX
(
name
,
in
,
out
)))
return
ERROR_NOT_ENOUGH_MEMORY
;
r
->
wDataLength
=
sizeof
(
DNS_PTR_DATAA
)
+
sizeof
(
DWORD
);
break
;
case
DNS_TYPE_SIG
:
if
(
pos
+
18
>
rrend
)
return
DNS_ERROR_BAD_PACKET
;
r
->
Data
.
SIG
.
wTypeCovered
=
get_word
(
&
pos
);
r
->
Data
.
SIG
.
chAlgorithm
=
*
pos
++
;
r
->
Data
.
SIG
.
chLabelCount
=
*
pos
++
;
r
->
Data
.
SIG
.
dwOriginalTtl
=
get_dword
(
&
pos
);
r
->
Data
.
SIG
.
dwExpiration
=
get_dword
(
&
pos
);
r
->
Data
.
SIG
.
dwTimeSigned
=
get_dword
(
&
pos
);
r
->
Data
.
SIG
.
wKeyTag
=
get_word
(
&
pos
);
if
(
!
(
pos
=
get_name
(
base
,
end
,
pos
,
name
)))
return
DNS_ERROR_BAD_PACKET
;
if
(
!
(
r
->
Data
.
SIG
.
pNameSigner
=
strcpyX
(
name
,
in
,
out
)))
return
ERROR_NOT_ENOUGH_MEMORY
;
r
->
Data
.
SIG
.
wSignatureLength
=
rrend
-
pos
;
memcpy
(
r
->
Data
.
SIG
.
Signature
,
pos
,
r
->
Data
.
SIG
.
wSignatureLength
);
r
->
wDataLength
=
offsetof
(
DNS_SIG_DATAA
,
Signature
[
r
->
Data
.
SIG
.
wSignatureLength
]
);
break
;
case
DNS_TYPE_SOA
:
if
(
!
(
pos
=
get_name
(
base
,
end
,
pos
,
name
)))
return
DNS_ERROR_BAD_PACKET
;
if
(
!
(
r
->
Data
.
SOA
.
pNamePrimaryServer
=
strcpyX
(
name
,
in
,
out
)))
return
ERROR_NOT_ENOUGH_MEMORY
;
if
(
!
(
pos
=
get_name
(
base
,
end
,
pos
,
name
)))
return
DNS_ERROR_BAD_PACKET
;
if
(
!
(
r
->
Data
.
SOA
.
pNameAdministrator
=
strcpyX
(
name
,
in
,
out
)))
{
heap_free
(
r
->
Data
.
SOA
.
pNamePrimaryServer
);
return
ERROR_NOT_ENOUGH_MEMORY
;
}
if
(
pos
+
5
*
sizeof
(
DWORD
)
>
rrend
)
return
DNS_ERROR_BAD_PACKET
;
r
->
Data
.
SOA
.
dwSerialNo
=
get_dword
(
&
pos
);
r
->
Data
.
SOA
.
dwRefresh
=
get_dword
(
&
pos
);
r
->
Data
.
SOA
.
dwRetry
=
get_dword
(
&
pos
);
r
->
Data
.
SOA
.
dwExpire
=
get_dword
(
&
pos
);
r
->
Data
.
SOA
.
dwDefaultTtl
=
get_dword
(
&
pos
);
r
->
wDataLength
=
sizeof
(
DNS_SOA_DATAA
);
break
;
case
DNS_TYPE_SRV
:
if
(
pos
+
3
*
sizeof
(
WORD
)
>
rrend
)
return
DNS_ERROR_BAD_PACKET
;
r
->
Data
.
SRV
.
wPriority
=
get_word
(
&
pos
);
r
->
Data
.
SRV
.
wWeight
=
get_word
(
&
pos
);
r
->
Data
.
SRV
.
wPort
=
get_word
(
&
pos
);
if
(
!
get_name
(
base
,
end
,
pos
,
name
))
return
DNS_ERROR_BAD_PACKET
;
if
(
!
(
r
->
Data
.
SRV
.
pNameTarget
=
strcpyX
(
name
,
in
,
out
)))
return
ERROR_NOT_ENOUGH_MEMORY
;
r
->
wDataLength
=
sizeof
(
DNS_SRV_DATAA
);
break
;
case
DNS_TYPE_HINFO
:
case
DNS_TYPE_ISDN
:
case
DNS_TYPE_X25
:
case
DNS_TYPE_TEXT
:
for
(
i
=
0
;
pos
<
rrend
;
i
++
)
{
BYTE
len
=
pos
[
0
];
if
(
pos
+
len
+
1
>
rrend
)
return
DNS_ERROR_BAD_PACKET
;
memcpy
(
name
,
pos
+
1
,
len
);
name
[
len
]
=
0
;
if
(
!
(
r
->
Data
.
TXT
.
pStringArray
[
i
]
=
strcpyX
(
name
,
in
,
out
)))
{
while
(
i
>
0
)
heap_free
(
r
->
Data
.
TXT
.
pStringArray
[
--
i
]
);
return
ERROR_NOT_ENOUGH_MEMORY
;
}
pos
+=
len
+
1
;
}
r
->
Data
.
TXT
.
dwStringCount
=
i
;
r
->
wDataLength
=
offsetof
(
DNS_TXT_DATAA
,
pStringArray
[
r
->
Data
.
TXT
.
dwStringCount
]
);
break
;
case
DNS_TYPE_ATMA
:
case
DNS_TYPE_LOC
:
case
DNS_TYPE_NXT
:
case
DNS_TYPE_TSIG
:
case
DNS_TYPE_WKS
:
case
DNS_TYPE_TKEY
:
case
DNS_TYPE_WINS
:
case
DNS_TYPE_WINSR
:
default:
FIXME
(
"unhandled type: %s
\n
"
,
debugstr_type
(
type
));
return
DNS_ERROR_RCODE_NOT_IMPLEMENTED
;
}
return
ret
;
}
static
DNS_STATUS
extract_record
(
const
DNS_MESSAGE_BUFFER
*
hdr
,
const
BYTE
*
end
,
const
BYTE
**
pos
,
DNS_SECTION
section
,
DNS_CHARSET
charset
,
DNS_RECORDA
**
recp
)
{
DNS_STATUS
ret
;
DNS_RECORDA
*
record
;
char
name
[
DNS_MAX_NAME_BUFFER_LENGTH
];
WORD
type
,
rdlen
;
DWORD
ttl
;
const
BYTE
*
base
=
(
const
BYTE
*
)
hdr
;
const
BYTE
*
ptr
=
*
pos
;
if
(
!
(
ptr
=
get_name
(
base
,
end
,
ptr
,
name
)))
return
DNS_ERROR_BAD_PACKET
;
if
(
ptr
+
10
>
end
)
return
DNS_ERROR_BAD_PACKET
;
type
=
get_word
(
&
ptr
);
ptr
+=
sizeof
(
WORD
);
/* class */
ttl
=
get_dword
(
&
ptr
);
rdlen
=
get_word
(
&
ptr
);
if
(
ptr
+
rdlen
>
end
)
return
DNS_ERROR_BAD_PACKET
;
*
pos
=
ptr
+
rdlen
;
if
(
!
(
record
=
heap_alloc_zero
(
get_record_size
(
type
,
ptr
,
rdlen
)
)))
return
ERROR_NOT_ENOUGH_MEMORY
;
record
->
wType
=
type
;
record
->
Flags
.
S
.
Section
=
section
;
record
->
Flags
.
S
.
CharSet
=
charset
;
record
->
dwTtl
=
ttl
;
if
(
!
(
record
->
pName
=
strcpyX
(
name
,
DnsCharSetUtf8
,
charset
)))
{
heap_free
(
record
);
return
ERROR_NOT_ENOUGH_MEMORY
;
}
if
((
ret
=
extract_rdata
(
base
,
end
,
ptr
,
rdlen
,
type
,
record
)))
{
heap_free
(
record
->
pName
);
heap_free
(
record
);
return
ret
;
}
*
recp
=
record
;
TRACE
(
"found %s record in %s section
\n
"
,
debugstr_type
(
record
->
wType
),
debugstr_section
(
section
)
);
return
ERROR_SUCCESS
;
}
static
DNS_STATUS
extract_message_records
(
const
DNS_MESSAGE_BUFFER
*
buffer
,
WORD
len
,
DNS_CHARSET
charset
,
DNS_RRSET
*
rrset
)
{
DNS_STATUS
ret
=
ERROR_SUCCESS
;
const
DNS_HEADER
*
hdr
=
&
buffer
->
MessageHead
;
DNS_RECORDA
*
record
;
const
BYTE
*
base
=
(
const
BYTE
*
)
hdr
;
const
BYTE
*
end
=
base
+
len
;
const
BYTE
*
ptr
=
(
const
BYTE
*
)
buffer
->
MessageBody
;
unsigned
int
num
;
if
(
hdr
->
IsResponse
&&
!
hdr
->
AnswerCount
)
return
DNS_ERROR_BAD_PACKET
;
for
(
num
=
0
;
num
<
hdr
->
QuestionCount
;
num
++
)
if
(
!
(
ptr
=
skip_record
(
ptr
,
end
,
DnsSectionQuestion
)))
return
DNS_ERROR_BAD_PACKET
;
for
(
num
=
0
;
num
<
hdr
->
AnswerCount
;
num
++
)
{
if
((
ret
=
extract_record
(
buffer
,
end
,
&
ptr
,
DnsSectionAnswer
,
charset
,
&
record
)))
return
ret
;
DNS_RRSET_ADD
(
*
rrset
,
(
DNS_RECORD
*
)
record
);
}
for
(
num
=
0
;
num
<
hdr
->
NameServerCount
;
num
++
)
if
(
!
(
ptr
=
skip_record
(
ptr
,
end
,
DnsSectionAuthority
)))
return
DNS_ERROR_BAD_PACKET
;
for
(
num
=
0
;
num
<
hdr
->
AdditionalCount
;
num
++
)
{
if
((
ret
=
extract_record
(
buffer
,
end
,
&
ptr
,
DnsSectionAddtional
,
charset
,
&
record
)))
return
ret
;
DNS_RRSET_ADD
(
*
rrset
,
(
DNS_RECORD
*
)
record
);
}
if
(
ptr
!=
end
)
ret
=
DNS_ERROR_BAD_PACKET
;
return
ret
;
}
/******************************************************************************
* DnsExtractRecordsFromMessage_UTF8 [DNSAPI.@]
*
*/
DNS_STATUS
WINAPI
DnsExtractRecordsFromMessage_UTF8
(
DNS_MESSAGE_BUFFER
*
buffer
,
WORD
len
,
DNS_RECORDA
**
result
)
{
DNS_STATUS
ret
=
DNS_ERROR_BAD_PACKET
;
DNS_RRSET
rrset
;
if
(
len
<
sizeof
(
*
buffer
))
return
ERROR_INVALID_PARAMETER
;
DNS_RRSET_INIT
(
rrset
);
ret
=
extract_message_records
(
buffer
,
len
,
DnsCharSetUtf8
,
&
rrset
);
DNS_RRSET_TERMINATE
(
rrset
);
if
(
!
ret
)
*
result
=
(
DNS_RECORDA
*
)
rrset
.
pFirstRR
;
else
DnsRecordListFree
(
rrset
.
pFirstRR
,
DnsFreeRecordList
);
return
ret
;
}
/******************************************************************************
* DnsExtractRecordsFromMessage_W [DNSAPI.@]
*
*/
DNS_STATUS
WINAPI
DnsExtractRecordsFromMessage_W
(
DNS_MESSAGE_BUFFER
*
buffer
,
WORD
len
,
DNS_RECORDW
**
result
)
{
DNS_STATUS
ret
=
DNS_ERROR_BAD_PACKET
;
DNS_RRSET
rrset
;
if
(
len
<
sizeof
(
*
buffer
))
return
ERROR_INVALID_PARAMETER
;
DNS_RRSET_INIT
(
rrset
);
ret
=
extract_message_records
(
buffer
,
len
,
DnsCharSetUnicode
,
&
rrset
);
DNS_RRSET_TERMINATE
(
rrset
);
if
(
!
ret
)
*
result
=
(
DNS_RECORDW
*
)
rrset
.
pFirstRR
;
else
DnsRecordListFree
(
rrset
.
pFirstRR
,
DnsFreeRecordList
);
return
ret
;
}
dlls/dnsapi/tests/record.c
View file @
aec19e86
...
...
@@ -138,9 +138,208 @@ static void test_DnsRecordSetDetach( void )
ok
(
!
r2
.
pNext
,
"failed unexpectedly
\n
"
);
}
static
BYTE
msg_empty
[]
=
/* empty message */
{
0x12
,
0x34
,
0x00
,
0x00
,
0x00
,
0x01
,
0x00
,
0x01
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
};
static
BYTE
msg_empty_answer
[]
=
/* marked as answer but contains only question */
{
0x12
,
0x34
,
0x80
,
0x00
,
0x01
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x01
,
0x00
,
0x01
};
static
BYTE
msg_question
[]
=
/* question only */
{
0x12
,
0x34
,
0x00
,
0x00
,
0x01
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x01
,
0x00
,
0x01
};
static
BYTE
msg_winehq
[]
=
/* valid answer for winehq.org */
{
0x12
,
0x34
,
0x81
,
0x80
,
0x01
,
0x00
,
0x01
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x06
,
'w'
,
'i'
,
'n'
,
'e'
,
'h'
,
'q'
,
0x03
,
'o'
,
'r'
,
'g'
,
0x00
,
0x00
,
0x01
,
0x00
,
0x01
,
0xc0
,
0x0c
,
0x00
,
0x01
,
0x00
,
0x01
,
0x04
,
0x05
,
0x06
,
0x07
,
0x00
,
0x04
,
0x04
,
0x04
,
0x51
,
0x7c
};
static
BYTE
msg_invchars
[]
=
/* invalid characters in name */
{
0x12
,
0x34
,
0x81
,
0x80
,
0x01
,
0x00
,
0x01
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x02
,
'a'
,
'$'
,
0x02
,
'b'
,
'\\'
,
0x02
,
'c'
,
'.'
,
0x02
,
0x09
,
'd'
,
0x00
,
0x00
,
0x01
,
0x00
,
0x01
,
0xc0
,
0x0c
,
0x00
,
0x01
,
0x00
,
0x01
,
0x00
,
0x00
,
0x00
,
0x01
,
0x00
,
0x04
,
0x04
,
0x04
,
0x51
,
0x7c
};
static
BYTE
msg_root
[]
=
/* root name */
{
0x12
,
0x34
,
0x81
,
0x80
,
0x01
,
0x00
,
0x01
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x01
,
0x00
,
0x01
,
0xc0
,
0x0c
,
0x00
,
0x01
,
0x00
,
0x01
,
0x00
,
0x00
,
0x00
,
0x01
,
0x00
,
0x04
,
0x04
,
0x04
,
0x51
,
0x7c
};
static
BYTE
msg_label
[]
=
/* name with binary label, not supported on Windows */
{
0x12
,
0x34
,
0x81
,
0x80
,
0x01
,
0x00
,
0x01
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x03
,
'a'
,
'b'
,
'c'
,
0x41
,
0x01
,
0x34
,
0x00
,
0x00
,
0x01
,
0x00
,
0x01
,
0xc0
,
0x0c
,
0x00
,
0x01
,
0x00
,
0x01
,
0x00
,
0x00
,
0x00
,
0x01
,
0x00
,
0x04
,
0x04
,
0x04
,
0x51
,
0x7c
};
static
BYTE
msg_loop
[]
=
/* name with loop */
{
0x12
,
0x34
,
0x81
,
0x80
,
0x01
,
0x00
,
0x01
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0xc0
,
0x0c
,
0x00
,
0x01
,
0x00
,
0x01
,
0xc0
,
0x0c
,
0x00
,
0x01
,
0x00
,
0x01
,
0x00
,
0x00
,
0x00
,
0x01
,
0x00
,
0x04
,
0x04
,
0x04
,
0x51
,
0x7c
};
static
BYTE
msg_types
[]
=
/* various record types */
{
0x12
,
0x34
,
0x81
,
0x80
,
0x01
,
0x00
,
0x05
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x06
,
'w'
,
'i'
,
'n'
,
'e'
,
'h'
,
'q'
,
0x03
,
'o'
,
'r'
,
'g'
,
0x00
,
0x00
,
0x01
,
0x00
,
0x01
,
/* CNAME */
0xc0
,
0x0c
,
0x00
,
0x05
,
0x00
,
0x01
,
0x04
,
0x05
,
0x06
,
0x07
,
0x00
,
0x05
,
0x01
,
'a'
,
0x01
,
'b'
,
0x00
,
/* MX */
0xc0
,
0x0c
,
0x00
,
0x0f
,
0x00
,
0x01
,
0x04
,
0x05
,
0x06
,
0x07
,
0x00
,
0x07
,
0x03
,
0x04
,
0x01
,
'c'
,
0x01
,
'd'
,
0x00
,
/* AAAA */
0xc0
,
0x0c
,
0x00
,
0x1c
,
0x00
,
0x01
,
0x04
,
0x05
,
0x06
,
0x07
,
0x00
,
0x10
,
0x01
,
0x02
,
0x03
,
0x04
,
0x05
,
0x06
,
0x07
,
0x08
,
0x09
,
0x0a
,
0x0b
,
0x0c
,
0x0d
,
0x0e
,
0x0f
,
0x00
,
/* KEY */
0xc0
,
0x0c
,
0x00
,
0x19
,
0x00
,
0x01
,
0x04
,
0x05
,
0x06
,
0x07
,
0x00
,
0x06
,
0x11
,
0x22
,
0x33
,
0x44
,
0x55
,
0x66
,
/* TXT */
0x01
,
't'
,
0x01
,
'x'
,
0x00
,
0x00
,
0x10
,
0x00
,
0x01
,
0x04
,
0x05
,
0x06
,
0x07
,
0x00
,
0x09
,
0x02
,
'z'
,
'y'
,
0x00
,
0x04
,
'X'
,
'Y'
,
0xc3
,
0xa9
};
static
void
test_DnsExtractRecordsFromMessage
(
void
)
{
DNS_STATUS
ret
;
DNS_RECORDA
*
rec
,
*
r
;
DNS_RECORDW
*
recW
,
*
rW
;
ret
=
DnsExtractRecordsFromMessage_UTF8
(
(
DNS_MESSAGE_BUFFER
*
)
msg_empty
,
sizeof
(
msg_empty
)
-
1
,
&
rec
);
ok
(
ret
==
ERROR_INVALID_PARAMETER
||
broken
(
ret
==
DNS_ERROR_BAD_PACKET
)
/* win7 */
,
"failed %u
\n
"
,
ret
);
ret
=
DnsExtractRecordsFromMessage_UTF8
(
(
DNS_MESSAGE_BUFFER
*
)
msg_empty
,
sizeof
(
msg_empty
),
&
rec
);
ok
(
ret
==
DNS_ERROR_BAD_PACKET
,
"failed %u
\n
"
,
ret
);
ret
=
DnsExtractRecordsFromMessage_UTF8
(
(
DNS_MESSAGE_BUFFER
*
)
msg_empty_answer
,
sizeof
(
msg_empty_answer
),
&
rec
);
ok
(
ret
==
DNS_ERROR_BAD_PACKET
,
"failed %u
\n
"
,
ret
);
rec
=
(
void
*
)
0xdeadbeef
;
ret
=
DnsExtractRecordsFromMessage_UTF8
(
(
DNS_MESSAGE_BUFFER
*
)
msg_question
,
sizeof
(
msg_question
),
&
rec
);
ok
(
!
ret
,
"failed %u
\n
"
,
ret
);
ok
(
!
rec
,
"record %p
\n
"
,
rec
);
rec
=
(
void
*
)
0xdeadbeef
;
ret
=
DnsExtractRecordsFromMessage_UTF8
(
(
DNS_MESSAGE_BUFFER
*
)
msg_winehq
,
sizeof
(
msg_winehq
),
&
rec
);
ok
(
!
ret
,
"failed %u
\n
"
,
ret
);
ok
(
rec
!=
NULL
,
"record not set
\n
"
);
ok
(
!
strcmp
(
rec
->
pName
,
"winehq.org"
),
"wrong name %s
\n
"
,
rec
->
pName
);
ok
(
rec
->
Flags
.
S
.
Section
==
DnsSectionAnswer
,
"wrong section %u
\n
"
,
rec
->
Flags
.
S
.
Section
);
ok
(
rec
->
Flags
.
S
.
CharSet
==
DnsCharSetUtf8
,
"wrong charset %u
\n
"
,
rec
->
Flags
.
S
.
CharSet
);
ok
(
rec
->
wType
==
DNS_TYPE_A
,
"wrong type %u
\n
"
,
rec
->
wType
);
ok
(
rec
->
wDataLength
==
sizeof
(
DNS_A_DATA
),
"wrong len %u
\n
"
,
rec
->
wDataLength
);
ok
(
rec
->
dwTtl
==
0x04050607
,
"wrong ttl %x
\n
"
,
rec
->
dwTtl
);
ok
(
rec
->
Data
.
A
.
IpAddress
==
0x7c510404
,
"wrong addr %08x
\n
"
,
rec
->
Data
.
A
.
IpAddress
);
ok
(
!
rec
->
pNext
,
"next record %p
\n
"
,
rec
->
pNext
);
DnsRecordListFree
(
(
DNS_RECORD
*
)
rec
,
DnsFreeRecordList
);
recW
=
(
void
*
)
0xdeadbeef
;
ret
=
DnsExtractRecordsFromMessage_W
(
(
DNS_MESSAGE_BUFFER
*
)
msg_winehq
,
sizeof
(
msg_winehq
),
&
recW
);
ok
(
!
ret
,
"failed %u
\n
"
,
ret
);
ok
(
recW
!=
NULL
,
"record not set
\n
"
);
ok
(
!
wcscmp
(
recW
->
pName
,
L"winehq.org"
),
"wrong name %s
\n
"
,
debugstr_w
(
recW
->
pName
)
);
DnsRecordListFree
(
(
DNS_RECORD
*
)
recW
,
DnsFreeRecordList
);
ret
=
DnsExtractRecordsFromMessage_UTF8
(
(
DNS_MESSAGE_BUFFER
*
)
msg_invchars
,
sizeof
(
msg_invchars
),
&
rec
);
ok
(
!
ret
,
"failed %u
\n
"
,
ret
);
ok
(
!
strcmp
(
rec
->
pName
,
"a$.b
\\
.c..
\t
d"
),
"wrong name %s
\n
"
,
rec
->
pName
);
DnsRecordListFree
(
(
DNS_RECORD
*
)
rec
,
DnsFreeRecordList
);
ret
=
DnsExtractRecordsFromMessage_UTF8
(
(
DNS_MESSAGE_BUFFER
*
)
msg_root
,
sizeof
(
msg_root
),
&
rec
);
ok
(
!
ret
,
"failed %u
\n
"
,
ret
);
ok
(
!
strcmp
(
rec
->
pName
,
"."
),
"wrong name %s
\n
"
,
rec
->
pName
);
DnsRecordListFree
(
(
DNS_RECORD
*
)
rec
,
DnsFreeRecordList
);
ret
=
DnsExtractRecordsFromMessage_UTF8
(
(
DNS_MESSAGE_BUFFER
*
)
msg_label
,
sizeof
(
msg_label
),
&
rec
);
ok
(
ret
==
DNS_ERROR_BAD_PACKET
,
"failed %u
\n
"
,
ret
);
ret
=
DnsExtractRecordsFromMessage_UTF8
(
(
DNS_MESSAGE_BUFFER
*
)
msg_loop
,
sizeof
(
msg_loop
),
&
rec
);
ok
(
ret
==
DNS_ERROR_BAD_PACKET
,
"failed %u
\n
"
,
ret
);
ret
=
DnsExtractRecordsFromMessage_UTF8
(
(
DNS_MESSAGE_BUFFER
*
)
msg_types
,
sizeof
(
msg_types
),
&
rec
);
ok
(
!
ret
,
"failed %u
\n
"
,
ret
);
r
=
rec
;
ok
(
r
!=
NULL
,
"record not set
\n
"
);
ok
(
!
strcmp
(
r
->
pName
,
"winehq.org"
),
"wrong name %s
\n
"
,
r
->
pName
);
ok
(
r
->
wType
==
DNS_TYPE_CNAME
,
"wrong type %u
\n
"
,
r
->
wType
);
ok
(
!
strcmp
(
r
->
Data
.
CNAME
.
pNameHost
,
"a.b"
),
"wrong cname %s
\n
"
,
r
->
Data
.
CNAME
.
pNameHost
);
ok
(
r
->
wDataLength
==
sizeof
(
DNS_PTR_DATAA
)
+
4
,
"wrong len %x
\n
"
,
r
->
wDataLength
);
r
=
r
->
pNext
;
ok
(
r
!=
NULL
,
"record not set
\n
"
);
ok
(
!
strcmp
(
r
->
pName
,
"winehq.org"
),
"wrong name %s
\n
"
,
r
->
pName
);
ok
(
r
->
wType
==
DNS_TYPE_MX
,
"wrong type %u
\n
"
,
r
->
wType
);
ok
(
r
->
Data
.
MX
.
wPreference
==
0x0304
,
"wrong pref %x
\n
"
,
r
->
Data
.
MX
.
wPreference
);
ok
(
!
strcmp
(
r
->
Data
.
MX
.
pNameExchange
,
"c.d"
),
"wrong mx %s
\n
"
,
r
->
Data
.
MX
.
pNameExchange
);
ok
(
r
->
wDataLength
==
sizeof
(
DNS_MX_DATAA
)
+
4
,
"wrong len %x
\n
"
,
r
->
wDataLength
);
r
=
r
->
pNext
;
ok
(
r
!=
NULL
,
"record not set
\n
"
);
ok
(
!
strcmp
(
r
->
pName
,
"winehq.org"
),
"wrong name %s
\n
"
,
r
->
pName
);
ok
(
r
->
wType
==
DNS_TYPE_AAAA
,
"wrong type %u
\n
"
,
r
->
wType
);
ok
(
r
->
Data
.
AAAA
.
Ip6Address
.
IP6Dword
[
0
]
==
0x04030201
,
"wrong addr %x
\n
"
,
r
->
Data
.
AAAA
.
Ip6Address
.
IP6Dword
[
0
]
);
ok
(
r
->
Data
.
AAAA
.
Ip6Address
.
IP6Dword
[
1
]
==
0x08070605
,
"wrong addr %x
\n
"
,
r
->
Data
.
AAAA
.
Ip6Address
.
IP6Dword
[
1
]
);
ok
(
r
->
Data
.
AAAA
.
Ip6Address
.
IP6Dword
[
2
]
==
0x0c0b0a09
,
"wrong addr %x
\n
"
,
r
->
Data
.
AAAA
.
Ip6Address
.
IP6Dword
[
2
]
);
ok
(
r
->
Data
.
AAAA
.
Ip6Address
.
IP6Dword
[
3
]
==
0x000f0e0d
,
"wrong addr %x
\n
"
,
r
->
Data
.
AAAA
.
Ip6Address
.
IP6Dword
[
3
]
);
ok
(
r
->
wDataLength
==
sizeof
(
DNS_AAAA_DATA
),
"wrong len %x
\n
"
,
r
->
wDataLength
);
r
=
r
->
pNext
;
ok
(
r
!=
NULL
,
"record not set
\n
"
);
ok
(
!
strcmp
(
r
->
pName
,
"winehq.org"
),
"wrong name %s
\n
"
,
r
->
pName
);
ok
(
r
->
wType
==
DNS_TYPE_KEY
,
"wrong type %u
\n
"
,
r
->
wType
);
ok
(
r
->
Data
.
KEY
.
wFlags
==
0x1122
,
"wrong flags %x
\n
"
,
r
->
Data
.
KEY
.
wFlags
);
ok
(
r
->
Data
.
KEY
.
chProtocol
==
0x33
,
"wrong protocol %x
\n
"
,
r
->
Data
.
KEY
.
chProtocol
);
ok
(
r
->
Data
.
KEY
.
chAlgorithm
==
0x44
,
"wrong algorithm %x
\n
"
,
r
->
Data
.
KEY
.
chAlgorithm
);
ok
(
r
->
Data
.
KEY
.
wKeyLength
==
0x02
,
"wrong len %x
\n
"
,
r
->
Data
.
KEY
.
wKeyLength
);
ok
(
r
->
Data
.
KEY
.
Key
[
0
]
==
0x55
,
"wrong key %x
\n
"
,
r
->
Data
.
KEY
.
Key
[
0
]
);
ok
(
r
->
Data
.
KEY
.
Key
[
1
]
==
0x66
,
"wrong key %x
\n
"
,
r
->
Data
.
KEY
.
Key
[
1
]
);
ok
(
r
->
wDataLength
==
offsetof
(
DNS_KEY_DATA
,
Key
[
r
->
Data
.
KEY
.
wKeyLength
]
),
"wrong len %x
\n
"
,
r
->
wDataLength
);
r
=
r
->
pNext
;
ok
(
r
!=
NULL
,
"record not set
\n
"
);
ok
(
!
strcmp
(
r
->
pName
,
"t.x"
),
"wrong name %s
\n
"
,
r
->
pName
);
ok
(
r
->
wType
==
DNS_TYPE_TEXT
,
"wrong type %u
\n
"
,
r
->
wType
);
ok
(
r
->
Data
.
TXT
.
dwStringCount
==
3
,
"wrong count %u
\n
"
,
r
->
Data
.
TXT
.
dwStringCount
);
ok
(
!
strcmp
(
r
->
Data
.
TXT
.
pStringArray
[
0
],
"zy"
),
"wrong string %s
\n
"
,
r
->
Data
.
TXT
.
pStringArray
[
0
]
);
ok
(
!
strcmp
(
r
->
Data
.
TXT
.
pStringArray
[
1
],
""
),
"wrong string %s
\n
"
,
r
->
Data
.
TXT
.
pStringArray
[
1
]
);
ok
(
!
strcmp
(
r
->
Data
.
TXT
.
pStringArray
[
2
],
"XY
\xc3\xa9
"
),
"wrong string %s
\n
"
,
r
->
Data
.
TXT
.
pStringArray
[
2
]
);
ok
(
r
->
wDataLength
==
offsetof
(
DNS_TXT_DATAA
,
pStringArray
[
r
->
Data
.
TXT
.
dwStringCount
]
),
"wrong len %x
\n
"
,
r
->
wDataLength
);
ok
(
!
r
->
pNext
,
"next record %p
\n
"
,
r
->
pNext
);
DnsRecordListFree
(
(
DNS_RECORD
*
)
rec
,
DnsFreeRecordList
);
ret
=
DnsExtractRecordsFromMessage_W
(
(
DNS_MESSAGE_BUFFER
*
)
msg_types
,
sizeof
(
msg_types
),
&
recW
);
ok
(
!
ret
,
"failed %u
\n
"
,
ret
);
rW
=
recW
;
ok
(
rW
!=
NULL
,
"record not set
\n
"
);
ok
(
!
wcscmp
(
rW
->
pName
,
L"winehq.org"
),
"wrong name %s
\n
"
,
debugstr_w
(
rW
->
pName
)
);
ok
(
rW
->
wType
==
DNS_TYPE_CNAME
,
"wrong type %u
\n
"
,
rW
->
wType
);
ok
(
!
wcscmp
(
rW
->
Data
.
CNAME
.
pNameHost
,
L"a.b"
),
"wrong cname %s
\n
"
,
debugstr_w
(
rW
->
Data
.
CNAME
.
pNameHost
)
);
rW
=
rW
->
pNext
;
ok
(
rW
!=
NULL
,
"record not set
\n
"
);
ok
(
!
wcscmp
(
rW
->
pName
,
L"winehq.org"
),
"wrong name %s
\n
"
,
debugstr_w
(
rW
->
pName
)
);
ok
(
rW
->
wType
==
DNS_TYPE_MX
,
"wrong type %u
\n
"
,
rW
->
wType
);
ok
(
!
wcscmp
(
rW
->
Data
.
MX
.
pNameExchange
,
L"c.d"
),
"wrong mx %s
\n
"
,
debugstr_w
(
rW
->
Data
.
MX
.
pNameExchange
)
);
rW
=
rW
->
pNext
;
ok
(
r
!=
NULL
,
"record not set
\n
"
);
ok
(
!
wcscmp
(
rW
->
pName
,
L"winehq.org"
),
"wrong name %s
\n
"
,
debugstr_w
(
rW
->
pName
)
);
ok
(
rW
->
wType
==
DNS_TYPE_AAAA
,
"wrong type %u
\n
"
,
rW
->
wType
);
rW
=
rW
->
pNext
;
ok
(
r
!=
NULL
,
"record not set
\n
"
);
ok
(
!
wcscmp
(
rW
->
pName
,
L"winehq.org"
),
"wrong name %s
\n
"
,
debugstr_w
(
rW
->
pName
)
);
rW
=
rW
->
pNext
;
ok
(
r
!=
NULL
,
"record not set
\n
"
);
ok
(
!
wcscmp
(
rW
->
pName
,
L"t.x"
),
"wrong name %s
\n
"
,
debugstr_w
(
rW
->
pName
)
);
ok
(
rW
->
wType
==
DNS_TYPE_TEXT
,
"wrong type %u
\n
"
,
rW
->
wType
);
ok
(
rW
->
Data
.
TXT
.
dwStringCount
==
3
,
"wrong count %u
\n
"
,
rW
->
Data
.
TXT
.
dwStringCount
);
ok
(
!
wcscmp
(
rW
->
Data
.
TXT
.
pStringArray
[
0
],
L"zy"
),
"wrong string %s
\n
"
,
debugstr_w
(
rW
->
Data
.
TXT
.
pStringArray
[
0
])
);
ok
(
!
wcscmp
(
rW
->
Data
.
TXT
.
pStringArray
[
1
],
L""
),
"wrong string %s
\n
"
,
debugstr_w
(
rW
->
Data
.
TXT
.
pStringArray
[
1
])
);
ok
(
!
wcscmp
(
rW
->
Data
.
TXT
.
pStringArray
[
2
],
L"XY
\x00e9
"
),
"wrong string %s
\n
"
,
debugstr_w
(
rW
->
Data
.
TXT
.
pStringArray
[
2
])
);
ok
(
!
rW
->
pNext
,
"next record %p
\n
"
,
rW
->
pNext
);
DnsRecordListFree
(
(
DNS_RECORD
*
)
recW
,
DnsFreeRecordList
);
}
START_TEST
(
record
)
{
test_DnsRecordCompare
();
test_DnsRecordSetCompare
();
test_DnsRecordSetDetach
();
test_DnsExtractRecordsFromMessage
();
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment