Browse Source

more getters/setters

master
boB Rudis 1 year ago
parent
commit
81d09a3714
No known key found for this signature in database GPG Key ID: 1D7529BE14E2BBA9
30 changed files with 810 additions and 50 deletions
  1. +1
    -1
      DESCRIPTION
  2. +10
    -0
      NAMESPACE
  3. +84
    -3
      R/RcppExports.R
  4. +1
    -1
      R/clandnstine-package.R
  5. +3
    -1
      R/print-context.R
  6. +2
    -1
      R/resolver.R
  7. +21
    -0
      R/set-res-types.R
  8. +24
    -0
      R/set-transports.R
  9. +12
    -3
      README.Rmd
  10. +33
    -15
      README.md
  11. +1
    -1
      man/clandnstine.Rd
  12. +14
    -0
      man/gdns_get_hosts.Rd
  13. +18
    -0
      man/gdns_get_resolution_type.Rd
  14. +14
    -0
      man/gdns_get_resolvconf.Rd
  15. +14
    -0
      man/gdns_get_tls_ca_file.Rd
  16. +14
    -0
      man/gdns_get_tls_ca_path.Rd
  17. +14
    -0
      man/gdns_get_transports.Rd
  18. +3
    -0
      man/gdns_query.Rd
  19. +20
    -0
      man/gdns_set_resolution_type.Rd
  20. +16
    -0
      man/gdns_set_tls_ca_file.Rd
  21. +16
    -0
      man/gdns_set_tls_ca_path.Rd
  22. +22
    -0
      man/gdns_set_transports.Rd
  23. +12
    -0
      man/int_gdns_context.Rd
  24. +0
    -12
      man/int_gdns_resolver.Rd
  25. +12
    -0
      man/int_gdns_set_resolution_type.Rd
  26. +12
    -0
      man/int_gdns_set_transports.Rd
  27. +129
    -5
      src/RcppExports.cpp
  28. +233
    -2
      src/clandnstine-main.cpp
  29. +32
    -3
      src/resolver.cpp
  30. +23
    -2
      tests/testthat/test-clandnstine.R

+ 1
- 1
DESCRIPTION View File

@@ -1,6 +1,6 @@
Package: clandnstine
Type: Package
Title: Perform 'DNS' over 'TLS' Queries
Title: Perform Secure-by-default 'DNS' Queries
Version: 0.1.0
Date: 2019-01-18
Authors@R: c(


+ 10
- 0
NAMESPACE View File

@@ -5,12 +5,22 @@ S3method(print,gdns_response)
export("%>%")
export(gdns_context)
export(gdns_get_address)
export(gdns_get_hosts)
export(gdns_get_resolution_type)
export(gdns_get_resolvconf)
export(gdns_get_timeout)
export(gdns_get_tls_ca_file)
export(gdns_get_tls_ca_path)
export(gdns_get_transports)
export(gdns_lib_version)
export(gdns_query)
export(gdns_set_hosts)
export(gdns_set_resolution_type)
export(gdns_set_round_robin_upstreams)
export(gdns_set_timeout)
export(gdns_set_tls_ca_file)
export(gdns_set_tls_ca_path)
export(gdns_set_transports)
export(gdns_update_resolvers)
importFrom(Rcpp,sourceCpp)
importFrom(jsonlite,fromJSON)


+ 84
- 3
R/RcppExports.R View File

@@ -48,6 +48,76 @@ int_gdns_set_hosts <- function(gctx, hosts) {
.Call(`_clandnstine_int_gdns_set_hosts`, gctx, hosts)
}

#' Internal version of set_transports()
#' @keywords internal
int_gdns_set_transports <- function(gctx, trans) {
.Call(`_clandnstine_int_gdns_set_transports`, gctx, trans)
}

#' Internal version of gdns_set_resolution_type()
#' @keywords internal
int_gdns_set_resolution_type <- function(gctx, res_type) {
.Call(`_clandnstine_int_gdns_set_resolution_type`, gctx, res_type)
}

#' Retreive what transports are used for DNS lookups.
#'
#' @param gctx gdns resolver context created with [gdns_resolver()]
#' @export
gdns_get_transports <- function(gctx) {
.Call(`_clandnstine_gdns_get_transports`, gctx)
}

#' Retreive the value of the localnames namespace
#'
#' @param gctx gdns resolver context created with [gdns_resolver()]
#' @export
gdns_get_hosts <- function(gctx) {
.Call(`_clandnstine_gdns_get_hosts`, gctx)
}

#' Retreive the value with which the context's upstream recursive servers and suffixes were initialized
#'
#' @param gctx gdns resolver context created with [gdns_resolver()]
#' @export
gdns_get_resolvconf <- function(gctx) {
.Call(`_clandnstine_gdns_get_resolvconf`, gctx)
}

#' Retreive the value with which the context's upstream recursive servers and suffixes were initialized
#'
#' @param gctx gdns resolver context created with [gdns_resolver()]
#' @export
gdns_get_tls_ca_path <- function(gctx) {
.Call(`_clandnstine_gdns_get_tls_ca_path`, gctx)
}

#' Retreive the file location with CA certificates for verification purposes
#'
#' @param gctx gdns resolver context created with [gdns_resolver()]
#' @export
gdns_get_tls_ca_file <- function(gctx) {
.Call(`_clandnstine_gdns_get_tls_ca_file`, gctx)
}

#' Specify where the location for CA certificates for verification purposes are located
#'
#' @param gctx gdns resolver context created with [gdns_resolver()]
#' @param ca_path directory with Certificate Authority certificates
#' @export
gdns_set_tls_ca_path <- function(gctx, ca_path) {
.Call(`_clandnstine_gdns_set_tls_ca_path`, gctx, ca_path)
}

#' Specify the file with CA certificates for verification purposes
#'
#' @param gctx gdns resolver context created with [gdns_resolver()]
#' @param ca_file file with Certificate Authority certificates
#' @export
gdns_set_tls_ca_file <- function(gctx, ca_file) {
.Call(`_clandnstine_gdns_set_tls_ca_file`, gctx, ca_file)
}

#' Test whether an object is an external pointer
#'
#' @param x object to test
@@ -64,10 +134,10 @@ is_null_xptr_ <- function(s) {
.Call(`_clandnstine_is_null_xptr_`, s)
}

#' Internal version of gdns_resolver
#' Internal version of gdns_context
#' @keywords internal
int_gdns_resolver <- function(resolvers) {
.Call(`_clandnstine_int_gdns_resolver`, resolvers)
int_gdns_context <- function(resolvers) {
.Call(`_clandnstine_int_gdns_context`, resolvers)
}

#' Resolve a host to an addrss
@@ -91,3 +161,14 @@ int_gdns_query <- function(gctx, name, rr, include_reporting = FALSE) {
.Call(`_clandnstine_int_gdns_query`, gctx, name, rr, include_reporting)
}

#' Get the current resolution type setting
#'
#' @param gctx gdns resolver context created with [gdns_resolver()]
#' @export
#' @examples
#' x <- gdns_context()
#' gdns_get_resolution_type(x)
gdns_get_resolution_type <- function(gctx) {
.Call(`_clandnstine_gdns_get_resolution_type`, gctx)
}


+ 1
- 1
R/clandnstine-package.R View File

@@ -1,4 +1,4 @@
#' Perform 'DNS' over 'TLS' Queries
#' Perform Secure-by-default 'DNS' Queries
#'
#' Something something 'DNS. Something something 'TLS'.
#' Something something 'getdns API/library'.


+ 3
- 1
R/print-context.R View File

@@ -12,7 +12,9 @@ print.gctx <- function(x, ...) {
"<gdns v", gdns_lib_version(),
" resolver context; resolvers: [",
paste0(int_get_resolvers(x), collapse=", "),
"]; timeout: ", prettyNum(gdns_get_timeout(x), big.mark=","), " ms",
"]; timeout: ", prettyNum(gdns_get_timeout(x), big.mark=","),
" ms; lookup transport(s): [", paste0(gdns_get_transports(x), collapse=", "),
"]; resolution type: ", gdns_get_resolution_type(x),
">", "\n", sep = ""
)
}

+ 2
- 1
R/resolver.R View File

@@ -10,7 +10,7 @@
#' x <- gdns_context()
#' x <- gdns_context("1.1.1.1")
gdns_context <- function(resolvers = "9.9.9.9") {
int_gdns_resolver(resolvers)
int_gdns_context(resolvers)
}

#' Changes the list of resolvers in an already created context for use in resolution functions
@@ -141,6 +141,7 @@ gdns_set_hosts<- function(gctx, hosts_file) {
#' - `x25`
#' - `zonemd`
#'
#' @note Local hosts files are ignored when using this `getdns` API endpoint
#' @param gctx gdns resolver context created with [gdns_resolver()]
#' @param name an entity to query for
#' @param rr_type what resource record type do you want to queyr for? See `Details`.


+ 21
- 0
R/set-res-types.R View File

@@ -0,0 +1,21 @@
.res_types <- c("stub" = 520L, "recursive" = 521L)

#' Specify whether DNS queries are performed with recursive lookups or as a stub resolver
#'
#' @param gctx gdns resolver context created with [gdns_resolver()]
#' @param res_type length 1 character vector of either "`stub`" or "`recursive`"
#' @export
#' @examples
#' x <- gdns_context()
#' x <- gdns_set_resolution_type(x, "stub")
gdns_set_resolution_type <- function(gctx, res_type = c("stub", "recursive")) {

match.arg(
unique(tolower(trimws(res_type))), c("stub", "recursive")
) -> transports

res_type <- unname(.res_types[res_type])

int_gdns_set_resolution_type(gctx, res_type)

}

+ 24
- 0
R/set-transports.R View File

@@ -0,0 +1,24 @@
.transport_trans <- c("tls" = 1202L, "tcp" = 1201L, "udp" = 1200L)

#' Specifies what transport(s) is/ar used for DNS lookups
#'
#' @param gctx gdns resolver context created with [gdns_resolver()]
#' @param transports character vector of any/all of "`udp`", "`tcp`" or "`tls`".
#' Order matters as it specifies that the library will use to try to
#' perform the lookups.
#' @export
#' @examples
#' x <- gdns_context()
#' x <- gdns_set_transports(x, "tls")
gdns_set_transports <- function(gctx, transports = c("tls", "udp", "tcp")) {

match.arg(
unique(tolower(trimws(transports))), c("tls", "udp", "tcp"),
several.ok = TRUE
) -> transports

transports <- unname(.transport_trans[transports])

int_gdns_set_transports(gctx, transports)

}

+ 12
- 3
README.Rmd View File

@@ -14,7 +14,7 @@ options(width=120)

# clandnstine

Perform 'DNS' over 'TLS' Queries
Perform Secure-by-default 'DNS' Queries

## Description

@@ -35,7 +35,7 @@ I've gotten this running on macOS and Ubuntu 16.04. For the latter I had to ensu

## TODO/WAT

I finally grok the getdns api so the package api is going to change wildly and fast. It's only going to support DNS over TLS but will support all types of DNS queries.
I finally grok the getdns api so the package api is going to change wildly and fast. The default mode will be to perform queries using DNS over TLS but also supports UDP and TCP transports.

## Why?

@@ -85,15 +85,22 @@ It's stupid slow, consumes more CPU and bandwidth but forces adversaries to work

The following functions are implemented:


- `gdns_context`: Create a gdns DNS over TLS context and populate it with a resolver for use in resolution functions
- `gdns_get_address`: Resolve a host to an addrss
- `gdns_get_resolution_type`: Get the current resolution type setting
- `gdns_get_timeout`: Retreive the number of milliseconds to wait for request to return
- `gdns_get_tls_ca_file`: Retreive the file location with CA certificates for verification purposes
- `gdns_get_tls_ca_path`: Retreive the value with which the context's upstream recursive servers and suffixes were initialized
- `gdns_get_transports`: Retreive what transports are used for DNS lookups.
- `gdns_lib_version`: Return gdns library version
- `gdns_query`: Arbitrary DNS queries
- `gdns_set_hosts`: Initialized the context's local names namespace with values from the given hosts file.
- `gdns_set_resolution_type`: Specify whether DNS queries are performed with recursive lookups or as a stub resolver
- `gdns_set_round_robin_upstreams`: Set/unset context to round robin queries over the available upstreams when resolving with the stub resolution type.
- `gdns_set_timeout`: Specify the number of milliseconds to wait for request to return
- `gdns_set_tls_ca_file`: Specify the file with CA certificates for verification purposes
- `gdns_set_tls_ca_path`: Specify where the location for CA certificates for verification purposes are located
- `gdns_set_transports`: Specifies what transport(s) is/ar used for DNS lookups
- `gdns_update_resolvers`: Changes the list of resolvers in an already created context for use in resolution functions

## Installation
@@ -129,6 +136,8 @@ gdns_lib_version()

(gdns_update_resolvers(x, "1.1.1.1"))

(gdns_set_transports(x, c("udp", "tls", "tcp")))

(gdns_get_address(x, "rud.is"))

(gdns_get_address(x, "yahoo.com"))


+ 33
- 15
README.md View File

@@ -7,7 +7,7 @@ Status](https://codecov.io/gh/hrbrmstr/clandnstine/branch/master/graph/badge.svg

# clandnstine

Perform ‘DNS’ over ‘TLS’ Queries
Perform Secure-by-default ‘DNS’ Queries

## Description

@@ -36,8 +36,8 @@ extract it and `config`/`make`/`make install` (plus `ldconfig` after).
## TODO/WAT

I finally grok the getdns api so the package api is going to change
wildly and fast. It’s only going to support DNS over TLS but will
support all types of DNS queries.
wildly and fast. The default mode will be to perform queries using DNS
over TLS but also supports UDP and TCP transports.

## Why?

@@ -114,17 +114,32 @@ The following functions are implemented:
- `gdns_context`: Create a gdns DNS over TLS context and populate it
with a resolver for use in resolution functions
- `gdns_get_address`: Resolve a host to an addrss
- `gdns_get_resolution_type`: Get the current resolution type setting
- `gdns_get_timeout`: Retreive the number of milliseconds to wait for
request to return
- `gdns_get_tls_ca_file`: Retreive the file location with CA
certificates for verification purposes
- `gdns_get_tls_ca_path`: Retreive the value with which the context’s
upstream recursive servers and suffixes were initialized
- `gdns_get_transports`: Retreive what transports are used for DNS
lookups.
- `gdns_lib_version`: Return gdns library version
- `gdns_query`: Arbitrary DNS queries
- `gdns_set_hosts`: Initialized the context’s local names namespace
with values from the given hosts file.
- `gdns_set_resolution_type`: Specify whether DNS queries are
performed with recursive lookups or as a stub resolver
- `gdns_set_round_robin_upstreams`: Set/unset context to round robin
queries over the available upstreams when resolving with the stub
resolution type.
- `gdns_set_timeout`: Specify the number of milliseconds to wait for
request to return
- `gdns_set_tls_ca_file`: Specify the file with CA certificates for
verification purposes
- `gdns_set_tls_ca_path`: Specify where the location for CA
certificates for verification purposes are located
- `gdns_set_transports`: Specifies what transport(s) is/ar used for
DNS lookups
- `gdns_update_resolvers`: Changes the list of resolvers in an already
created context for use in resolution functions

@@ -153,27 +168,30 @@ gdns_lib_version()
## [1] "1.5.1"

(x <- gdns_context())
## <gdns v1.5.1 resolver context; resolvers: [9.9.9.9]; timeout: 5,000 ms>
## <gdns v1.5.1 resolver context; resolvers: [9.9.9.9]; timeout: 5,000 ms; lookup transport(s): [tls]; resolution type: stub>

(x <- gdns_context("1.1.1.1"))
## <gdns v1.5.1 resolver context; resolvers: [1.1.1.1]; timeout: 5,000 ms>
## <gdns v1.5.1 resolver context; resolvers: [1.1.1.1]; timeout: 5,000 ms; lookup transport(s): [tls]; resolution type: stub>

(x <- gdns_context(c("8.8.8.8", "1.1.1.1", "9.9.9.9")))
## <gdns v1.5.1 resolver context; resolvers: [8.8.8.8, 1.1.1.1, 9.9.9.9]; timeout: 5,000 ms>
## <gdns v1.5.1 resolver context; resolvers: [8.8.8.8, 1.1.1.1, 9.9.9.9]; timeout: 5,000 ms; lookup transport(s): [tls]; resolution type: stub>

(gdns_set_timeout(x, 2000))
## <gdns v1.5.1 resolver context; resolvers: [8.8.8.8, 1.1.1.1, 9.9.9.9]; timeout: 2,000 ms>
## <gdns v1.5.1 resolver context; resolvers: [8.8.8.8, 1.1.1.1, 9.9.9.9]; timeout: 2,000 ms; lookup transport(s): [tls]; resolution type: stub>

(gdns_update_resolvers(x, "1.1.1.1"))
## <gdns v1.5.1 resolver context; resolvers: [1.1.1.1]; timeout: 2,000 ms>
## <gdns v1.5.1 resolver context; resolvers: [1.1.1.1]; timeout: 2,000 ms; lookup transport(s): [tls]; resolution type: stub>

(gdns_set_transports(x, c("udp", "tls", "tcp")))
## <gdns v1.5.1 resolver context; resolvers: [1.1.1.1]; timeout: 2,000 ms; lookup transport(s): [udp, tls, tcp]; resolution type: stub>

(gdns_get_address(x, "rud.is"))
## [1] "2604:a880:800:10::6bc:2001" "104.236.112.222"

(gdns_get_address(x, "yahoo.com"))
## [1] "2001:4998:44:41d::4" "2001:4998:58:1836::10" "2001:4998:58:1836::11" "2001:4998:c:1023::4"
## [5] "2001:4998:c:1023::5" "2001:4998:44:41d::3" "98.138.219.232" "72.30.35.9"
## [9] "72.30.35.10" "98.137.246.7" "98.137.246.8" "98.138.219.231"
## [1] "2001:4998:58:1836::10" "2001:4998:58:1836::11" "2001:4998:c:1023::4" "2001:4998:c:1023::5"
## [5] "2001:4998:44:41d::3" "2001:4998:44:41d::4" "72.30.35.9" "72.30.35.10"
## [9] "98.137.246.7" "98.137.246.8" "98.138.219.231" "98.138.219.232"

(gdns_get_address(x, "yahoo.commmm"))
## character(0)
@@ -186,7 +204,7 @@ str(leno <- gdns_query(x, "lenovo.com", "txt"), 1)
## List of 5
## $ answer_type : int 800
## $ canonical_name: chr "lenovo.com."
## $ replies_full : int [1, 1:936] 55 119 129 128 0 1 0 8 0 0 ...
## $ replies_full : int [1, 1:600] 165 144 129 128 0 1 0 8 0 0 ...
## $ replies_tree :'data.frame': 1 obs. of 7 variables:
## $ status : int 900
## - attr(*, "class")= chr [1:2] "gdns_response" "list"
@@ -209,9 +227,9 @@ Yep. Advertising even in DNS `TXT` records (see item number

| Lang | \# Files | (%) | LoC | (%) | Blank lines | (%) | \# Lines | (%) |
| :--- | -------: | ---: | --: | ---: | ----------: | ---: | -------: | ---: |
| C++ | 3 | 0.25 | 369 | 0.59 | 112 | 0.55 | 77 | 0.20 |
| R | 8 | 0.67 | 242 | 0.38 | 42 | 0.20 | 220 | 0.57 |
| Rmd | 1 | 0.08 | 18 | 0.03 | 51 | 0.25 | 89 | 0.23 |
| C++ | 3 | 0.21 | 608 | 0.65 | 196 | 0.62 | 138 | 0.27 |
| R | 10 | 0.71 | 306 | 0.33 | 68 | 0.22 | 280 | 0.54 |
| Rmd | 1 | 0.07 | 19 | 0.02 | 51 | 0.16 | 97 | 0.19 |

## Code of Conduct



+ 1
- 1
man/clandnstine.Rd View File

@@ -4,7 +4,7 @@
\name{clandnstine}
\alias{clandnstine}
\alias{clandnstine-package}
\title{Perform 'DNS' over 'TLS' Queries}
\title{Perform Secure-by-default 'DNS' Queries}
\description{
Something something 'DNS. Something something 'TLS'.
Something something 'getdns API/library'.


+ 14
- 0
man/gdns_get_hosts.Rd View File

@@ -0,0 +1,14 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/RcppExports.R
\name{gdns_get_hosts}
\alias{gdns_get_hosts}
\title{Retreive the value of the localnames namespace}
\usage{
gdns_get_hosts(gctx)
}
\arguments{
\item{gctx}{gdns resolver context created with \code{\link[=gdns_resolver]{gdns_resolver()}}}
}
\description{
Retreive the value of the localnames namespace
}

+ 18
- 0
man/gdns_get_resolution_type.Rd View File

@@ -0,0 +1,18 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/RcppExports.R
\name{gdns_get_resolution_type}
\alias{gdns_get_resolution_type}
\title{Get the current resolution type setting}
\usage{
gdns_get_resolution_type(gctx)
}
\arguments{
\item{gctx}{gdns resolver context created with \code{\link[=gdns_resolver]{gdns_resolver()}}}
}
\description{
Get the current resolution type setting
}
\examples{
x <- gdns_context()
gdns_get_resolution_type(x)
}

+ 14
- 0
man/gdns_get_resolvconf.Rd View File

@@ -0,0 +1,14 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/RcppExports.R
\name{gdns_get_resolvconf}
\alias{gdns_get_resolvconf}
\title{Retreive the value with which the context's upstream recursive servers and suffixes were initialized}
\usage{
gdns_get_resolvconf(gctx)
}
\arguments{
\item{gctx}{gdns resolver context created with \code{\link[=gdns_resolver]{gdns_resolver()}}}
}
\description{
Retreive the value with which the context's upstream recursive servers and suffixes were initialized
}

+ 14
- 0
man/gdns_get_tls_ca_file.Rd View File

@@ -0,0 +1,14 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/RcppExports.R
\name{gdns_get_tls_ca_file}
\alias{gdns_get_tls_ca_file}
\title{Retreive the file location with CA certificates for verification purposes}
\usage{
gdns_get_tls_ca_file(gctx)
}
\arguments{
\item{gctx}{gdns resolver context created with \code{\link[=gdns_resolver]{gdns_resolver()}}}
}
\description{
Retreive the file location with CA certificates for verification purposes
}

+ 14
- 0
man/gdns_get_tls_ca_path.Rd View File

@@ -0,0 +1,14 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/RcppExports.R
\name{gdns_get_tls_ca_path}
\alias{gdns_get_tls_ca_path}
\title{Retreive the value with which the context's upstream recursive servers and suffixes were initialized}
\usage{
gdns_get_tls_ca_path(gctx)
}
\arguments{
\item{gctx}{gdns resolver context created with \code{\link[=gdns_resolver]{gdns_resolver()}}}
}
\description{
Retreive the value with which the context's upstream recursive servers and suffixes were initialized
}

+ 14
- 0
man/gdns_get_transports.Rd View File

@@ -0,0 +1,14 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/RcppExports.R
\name{gdns_get_transports}
\alias{gdns_get_transports}
\title{Retreive what transports are used for DNS lookups.}
\usage{
gdns_get_transports(gctx)
}
\arguments{
\item{gctx}{gdns resolver context created with \code{\link[=gdns_resolver]{gdns_resolver()}}}
}
\description{
Retreive what transports are used for DNS lookups.
}

+ 3
- 0
man/gdns_query.Rd View File

@@ -117,6 +117,9 @@ Valid values for \code{rr_type}:
\item \code{zonemd}
}
}
\note{
Local hosts files are ignored when using this \code{getdns} API endpoint
}
\examples{
x <- gdns_resolver()
gdns_query(x, "example.com")


+ 20
- 0
man/gdns_set_resolution_type.Rd View File

@@ -0,0 +1,20 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/set-res-types.R
\name{gdns_set_resolution_type}
\alias{gdns_set_resolution_type}
\title{Specify whether DNS queries are performed with recursive lookups or as a stub resolver}
\usage{
gdns_set_resolution_type(gctx, res_type = c("stub", "recursive"))
}
\arguments{
\item{gctx}{gdns resolver context created with \code{\link[=gdns_resolver]{gdns_resolver()}}}

\item{res_type}{length 1 character vector of either "\code{stub}" or "\code{recursive}"}
}
\description{
Specify whether DNS queries are performed with recursive lookups or as a stub resolver
}
\examples{
x <- gdns_context()
x <- gdns_set_resolution_type(x, "stub")
}

+ 16
- 0
man/gdns_set_tls_ca_file.Rd View File

@@ -0,0 +1,16 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/RcppExports.R
\name{gdns_set_tls_ca_file}
\alias{gdns_set_tls_ca_file}
\title{Specify the file with CA certificates for verification purposes}
\usage{
gdns_set_tls_ca_file(gctx, ca_file)
}
\arguments{
\item{gctx}{gdns resolver context created with \code{\link[=gdns_resolver]{gdns_resolver()}}}

\item{ca_file}{file with Certificate Authority certificates}
}
\description{
Specify the file with CA certificates for verification purposes
}

+ 16
- 0
man/gdns_set_tls_ca_path.Rd View File

@@ -0,0 +1,16 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/RcppExports.R
\name{gdns_set_tls_ca_path}
\alias{gdns_set_tls_ca_path}
\title{Specify where the location for CA certificates for verification purposes are located}
\usage{
gdns_set_tls_ca_path(gctx, ca_path)
}
\arguments{
\item{gctx}{gdns resolver context created with \code{\link[=gdns_resolver]{gdns_resolver()}}}

\item{ca_path}{directory with Certificate Authority certificates}
}
\description{
Specify where the location for CA certificates for verification purposes are located
}

+ 22
- 0
man/gdns_set_transports.Rd View File

@@ -0,0 +1,22 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/set-transports.R
\name{gdns_set_transports}
\alias{gdns_set_transports}
\title{Specifies what transport(s) is/ar used for DNS lookups}
\usage{
gdns_set_transports(gctx, transports = c("tls", "udp", "tcp"))
}
\arguments{
\item{gctx}{gdns resolver context created with \code{\link[=gdns_resolver]{gdns_resolver()}}}

\item{transports}{character vector of any/all of "\code{udp}", "\code{tcp}" or "\code{tls}".
Order matters as it specifies that the library will use to try to
perform the lookups.}
}
\description{
Specifies what transport(s) is/ar used for DNS lookups
}
\examples{
x <- gdns_context()
x <- gdns_set_transports(x, "tls")
}

+ 12
- 0
man/int_gdns_context.Rd View File

@@ -0,0 +1,12 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/RcppExports.R
\name{int_gdns_context}
\alias{int_gdns_context}
\title{Internal version of gdns_context}
\usage{
int_gdns_context(resolvers)
}
\description{
Internal version of gdns_context
}
\keyword{internal}

+ 0
- 12
man/int_gdns_resolver.Rd View File

@@ -1,12 +0,0 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/RcppExports.R
\name{int_gdns_resolver}
\alias{int_gdns_resolver}
\title{Internal version of gdns_resolver}
\usage{
int_gdns_resolver(resolvers)
}
\description{
Internal version of gdns_resolver
}
\keyword{internal}

+ 12
- 0
man/int_gdns_set_resolution_type.Rd View File

@@ -0,0 +1,12 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/RcppExports.R
\name{int_gdns_set_resolution_type}
\alias{int_gdns_set_resolution_type}
\title{Internal version of gdns_set_resolution_type()}
\usage{
int_gdns_set_resolution_type(gctx, res_type)
}
\description{
Internal version of gdns_set_resolution_type()
}
\keyword{internal}

+ 12
- 0
man/int_gdns_set_transports.Rd View File

@@ -0,0 +1,12 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/RcppExports.R
\name{int_gdns_set_transports}
\alias{int_gdns_set_transports}
\title{Internal version of set_transports()}
\usage{
int_gdns_set_transports(gctx, trans)
}
\description{
Internal version of set_transports()
}
\keyword{internal}

+ 129
- 5
src/RcppExports.cpp View File

@@ -74,6 +74,109 @@ BEGIN_RCPP
return rcpp_result_gen;
END_RCPP
}
// int_gdns_set_transports
SEXP int_gdns_set_transports(SEXP gctx, IntegerVector trans);
RcppExport SEXP _clandnstine_int_gdns_set_transports(SEXP gctxSEXP, SEXP transSEXP) {
BEGIN_RCPP
Rcpp::RObject rcpp_result_gen;
Rcpp::RNGScope rcpp_rngScope_gen;
Rcpp::traits::input_parameter< SEXP >::type gctx(gctxSEXP);
Rcpp::traits::input_parameter< IntegerVector >::type trans(transSEXP);
rcpp_result_gen = Rcpp::wrap(int_gdns_set_transports(gctx, trans));
return rcpp_result_gen;
END_RCPP
}
// int_gdns_set_resolution_type
SEXP int_gdns_set_resolution_type(SEXP gctx, int res_type);
RcppExport SEXP _clandnstine_int_gdns_set_resolution_type(SEXP gctxSEXP, SEXP res_typeSEXP) {
BEGIN_RCPP
Rcpp::RObject rcpp_result_gen;
Rcpp::RNGScope rcpp_rngScope_gen;
Rcpp::traits::input_parameter< SEXP >::type gctx(gctxSEXP);
Rcpp::traits::input_parameter< int >::type res_type(res_typeSEXP);
rcpp_result_gen = Rcpp::wrap(int_gdns_set_resolution_type(gctx, res_type));
return rcpp_result_gen;
END_RCPP
}
// gdns_get_transports
CharacterVector gdns_get_transports(SEXP gctx);
RcppExport SEXP _clandnstine_gdns_get_transports(SEXP gctxSEXP) {
BEGIN_RCPP
Rcpp::RObject rcpp_result_gen;
Rcpp::RNGScope rcpp_rngScope_gen;
Rcpp::traits::input_parameter< SEXP >::type gctx(gctxSEXP);
rcpp_result_gen = Rcpp::wrap(gdns_get_transports(gctx));
return rcpp_result_gen;
END_RCPP
}
// gdns_get_hosts
CharacterVector gdns_get_hosts(SEXP gctx);
RcppExport SEXP _clandnstine_gdns_get_hosts(SEXP gctxSEXP) {
BEGIN_RCPP
Rcpp::RObject rcpp_result_gen;
Rcpp::RNGScope rcpp_rngScope_gen;
Rcpp::traits::input_parameter< SEXP >::type gctx(gctxSEXP);
rcpp_result_gen = Rcpp::wrap(gdns_get_hosts(gctx));
return rcpp_result_gen;
END_RCPP
}
// gdns_get_resolvconf
CharacterVector gdns_get_resolvconf(SEXP gctx);
RcppExport SEXP _clandnstine_gdns_get_resolvconf(SEXP gctxSEXP) {
BEGIN_RCPP
Rcpp::RObject rcpp_result_gen;
Rcpp::RNGScope rcpp_rngScope_gen;
Rcpp::traits::input_parameter< SEXP >::type gctx(gctxSEXP);
rcpp_result_gen = Rcpp::wrap(gdns_get_resolvconf(gctx));
return rcpp_result_gen;
END_RCPP
}
// gdns_get_tls_ca_path
StringVector gdns_get_tls_ca_path(SEXP gctx);
RcppExport SEXP _clandnstine_gdns_get_tls_ca_path(SEXP gctxSEXP) {
BEGIN_RCPP
Rcpp::RObject rcpp_result_gen;
Rcpp::RNGScope rcpp_rngScope_gen;
Rcpp::traits::input_parameter< SEXP >::type gctx(gctxSEXP);
rcpp_result_gen = Rcpp::wrap(gdns_get_tls_ca_path(gctx));
return rcpp_result_gen;
END_RCPP
}
// gdns_get_tls_ca_file
StringVector gdns_get_tls_ca_file(SEXP gctx);
RcppExport SEXP _clandnstine_gdns_get_tls_ca_file(SEXP gctxSEXP) {
BEGIN_RCPP
Rcpp::RObject rcpp_result_gen;
Rcpp::RNGScope rcpp_rngScope_gen;
Rcpp::traits::input_parameter< SEXP >::type gctx(gctxSEXP);
rcpp_result_gen = Rcpp::wrap(gdns_get_tls_ca_file(gctx));
return rcpp_result_gen;
END_RCPP
}
// gdns_set_tls_ca_path
SEXP gdns_set_tls_ca_path(SEXP gctx, std::string ca_path);
RcppExport SEXP _clandnstine_gdns_set_tls_ca_path(SEXP gctxSEXP, SEXP ca_pathSEXP) {
BEGIN_RCPP
Rcpp::RObject rcpp_result_gen;
Rcpp::RNGScope rcpp_rngScope_gen;
Rcpp::traits::input_parameter< SEXP >::type gctx(gctxSEXP);
Rcpp::traits::input_parameter< std::string >::type ca_path(ca_pathSEXP);
rcpp_result_gen = Rcpp::wrap(gdns_set_tls_ca_path(gctx, ca_path));
return rcpp_result_gen;
END_RCPP
}
// gdns_set_tls_ca_file
SEXP gdns_set_tls_ca_file(SEXP gctx, std::string ca_file);
RcppExport SEXP _clandnstine_gdns_set_tls_ca_file(SEXP gctxSEXP, SEXP ca_fileSEXP) {
BEGIN_RCPP
Rcpp::RObject rcpp_result_gen;
Rcpp::RNGScope rcpp_rngScope_gen;
Rcpp::traits::input_parameter< SEXP >::type gctx(gctxSEXP);
Rcpp::traits::input_parameter< std::string >::type ca_file(ca_fileSEXP);
rcpp_result_gen = Rcpp::wrap(gdns_set_tls_ca_file(gctx, ca_file));
return rcpp_result_gen;
END_RCPP
}
// check_is_xptr
void check_is_xptr(SEXP s);
RcppExport SEXP _clandnstine_check_is_xptr(SEXP sSEXP) {
@@ -95,14 +198,14 @@ BEGIN_RCPP
return rcpp_result_gen;
END_RCPP
}
// int_gdns_resolver
SEXP int_gdns_resolver(std::vector< std::string > resolvers);
RcppExport SEXP _clandnstine_int_gdns_resolver(SEXP resolversSEXP) {
// int_gdns_context
SEXP int_gdns_context(std::vector< std::string > resolvers);
RcppExport SEXP _clandnstine_int_gdns_context(SEXP resolversSEXP) {
BEGIN_RCPP
Rcpp::RObject rcpp_result_gen;
Rcpp::RNGScope rcpp_rngScope_gen;
Rcpp::traits::input_parameter< std::vector< std::string > >::type resolvers(resolversSEXP);
rcpp_result_gen = Rcpp::wrap(int_gdns_resolver(resolvers));
rcpp_result_gen = Rcpp::wrap(int_gdns_context(resolvers));
return rcpp_result_gen;
END_RCPP
}
@@ -143,6 +246,17 @@ BEGIN_RCPP
return rcpp_result_gen;
END_RCPP
}
// gdns_get_resolution_type
CharacterVector gdns_get_resolution_type(SEXP gctx);
RcppExport SEXP _clandnstine_gdns_get_resolution_type(SEXP gctxSEXP) {
BEGIN_RCPP
Rcpp::RObject rcpp_result_gen;
Rcpp::RNGScope rcpp_rngScope_gen;
Rcpp::traits::input_parameter< SEXP >::type gctx(gctxSEXP);
rcpp_result_gen = Rcpp::wrap(gdns_get_resolution_type(gctx));
return rcpp_result_gen;
END_RCPP
}

static const R_CallMethodDef CallEntries[] = {
{"_clandnstine_gdns_lib_version", (DL_FUNC) &_clandnstine_gdns_lib_version, 0},
@@ -151,12 +265,22 @@ static const R_CallMethodDef CallEntries[] = {
{"_clandnstine_gdns_get_timeout", (DL_FUNC) &_clandnstine_gdns_get_timeout, 1},
{"_clandnstine_gdns_set_round_robin_upstreams", (DL_FUNC) &_clandnstine_gdns_set_round_robin_upstreams, 2},
{"_clandnstine_int_gdns_set_hosts", (DL_FUNC) &_clandnstine_int_gdns_set_hosts, 2},
{"_clandnstine_int_gdns_set_transports", (DL_FUNC) &_clandnstine_int_gdns_set_transports, 2},
{"_clandnstine_int_gdns_set_resolution_type", (DL_FUNC) &_clandnstine_int_gdns_set_resolution_type, 2},
{"_clandnstine_gdns_get_transports", (DL_FUNC) &_clandnstine_gdns_get_transports, 1},
{"_clandnstine_gdns_get_hosts", (DL_FUNC) &_clandnstine_gdns_get_hosts, 1},
{"_clandnstine_gdns_get_resolvconf", (DL_FUNC) &_clandnstine_gdns_get_resolvconf, 1},
{"_clandnstine_gdns_get_tls_ca_path", (DL_FUNC) &_clandnstine_gdns_get_tls_ca_path, 1},
{"_clandnstine_gdns_get_tls_ca_file", (DL_FUNC) &_clandnstine_gdns_get_tls_ca_file, 1},
{"_clandnstine_gdns_set_tls_ca_path", (DL_FUNC) &_clandnstine_gdns_set_tls_ca_path, 2},
{"_clandnstine_gdns_set_tls_ca_file", (DL_FUNC) &_clandnstine_gdns_set_tls_ca_file, 2},
{"_clandnstine_check_is_xptr", (DL_FUNC) &_clandnstine_check_is_xptr, 1},
{"_clandnstine_is_null_xptr_", (DL_FUNC) &_clandnstine_is_null_xptr_, 1},
{"_clandnstine_int_gdns_resolver", (DL_FUNC) &_clandnstine_int_gdns_resolver, 1},
{"_clandnstine_int_gdns_context", (DL_FUNC) &_clandnstine_int_gdns_context, 1},
{"_clandnstine_gdns_get_address", (DL_FUNC) &_clandnstine_gdns_get_address, 2},
{"_clandnstine_int_get_resolvers", (DL_FUNC) &_clandnstine_int_get_resolvers, 1},
{"_clandnstine_int_gdns_query", (DL_FUNC) &_clandnstine_int_gdns_query, 4},
{"_clandnstine_gdns_get_resolution_type", (DL_FUNC) &_clandnstine_gdns_get_resolution_type, 1},
{NULL, NULL, 0}
};



+ 233
- 2
src/clandnstine-main.cpp View File

@@ -13,7 +13,6 @@ std::string gdns_lib_version() {
return(std::string(getdns_get_version()));
}


//' Internal version of gdns_update_resolvers
//' @keywords internal
// [[Rcpp::export]]
@@ -141,4 +140,236 @@ SEXP int_gdns_set_hosts(SEXP gctx, std::string hosts) {

return(gctx);

}
}

//' Internal version of set_transports()
//' @keywords internal
// [[Rcpp::export]]
SEXP int_gdns_set_transports(SEXP gctx, IntegerVector trans) {

check_is_xptr(gctx);

getdns_context *ctxt = (getdns_context *)R_ExternalPtrAddr(gctx);

if (gctx == NULL) return(R_NilValue);

getdns_return_t r;

getdns_transport_list_t tls_transport[trans.size()];
for (int i=0; i<trans.size(); i++) {
switch(trans[i]) {
case 1200 : tls_transport[i] = GETDNS_TRANSPORT_UDP; break;
case 1201 : tls_transport[i] = GETDNS_TRANSPORT_TCP; break;
case 1202 : tls_transport[i] = GETDNS_TRANSPORT_TLS; break;
}
}

if ((r = getdns_context_set_dns_transport_list(ctxt, trans.size(), tls_transport))) {
Rf_error(getdns_get_errorstr_by_id(r));
}

return(gctx);
}


//' Internal version of gdns_set_resolution_type()
//' @keywords internal
// [[Rcpp::export]]
SEXP int_gdns_set_resolution_type(SEXP gctx, int res_type) {

check_is_xptr(gctx);

getdns_context *ctxt = (getdns_context *)R_ExternalPtrAddr(gctx);

if (gctx == NULL) return(R_NilValue);

getdns_return_t r;

if ((r = getdns_context_set_resolution_type(ctxt, res_type == 520 ? GETDNS_RESOLUTION_STUB : GETDNS_RESOLUTION_RECURSING))) {
Rf_error(getdns_get_errorstr_by_id(r));
}

return(gctx);

}

//' Retreive what transports are used for DNS lookups.
//'
//' @param gctx gdns resolver context created with [gdns_resolver()]
//' @export
// [[Rcpp::export]]
CharacterVector gdns_get_transports(SEXP gctx) {

check_is_xptr(gctx);

getdns_context *ctxt = (getdns_context *)R_ExternalPtrAddr(gctx);

if (gctx == NULL) return(R_NilValue);

getdns_return_t r;
size_t sz;
getdns_transport_list_t *trans;

if ((r = getdns_context_get_dns_transport_list(ctxt, &sz, &trans))) {
Rf_error(getdns_get_errorstr_by_id(r));
}

CharacterVector out(sz);

for (int i=0; i<sz; i++) {
switch(trans[i]) {
case GETDNS_TRANSPORT_UDP : out[i] = "udp"; break;
case GETDNS_TRANSPORT_TCP : out[i] = "tcp"; break;
case GETDNS_TRANSPORT_TLS : out[i] = "tls"; break;
}
}

if (trans) free(trans);

return(out);

}

//' Retreive the value of the localnames namespace
//'
//' @param gctx gdns resolver context created with [gdns_resolver()]
//' @export
// [[Rcpp::export]]
CharacterVector gdns_get_hosts(SEXP gctx) {

check_is_xptr(gctx);

getdns_context *ctxt = (getdns_context *)R_ExternalPtrAddr(gctx);

if (gctx == NULL) return(R_NilValue);

getdns_return_t r;
const char *hosts;

if ((r = getdns_context_get_hosts(ctxt, &hosts))) {
Rf_error(getdns_get_errorstr_by_id(r));
}

return(std::string(hosts));

}

//' Retreive the value with which the context's upstream recursive servers and suffixes were initialized
//'
//' @param gctx gdns resolver context created with [gdns_resolver()]
//' @export
// [[Rcpp::export]]
CharacterVector gdns_get_resolvconf(SEXP gctx) {

check_is_xptr(gctx);

getdns_context *ctxt = (getdns_context *)R_ExternalPtrAddr(gctx);

if (gctx == NULL) return(R_NilValue);

getdns_return_t r;
const char *resolv;

if ((r = getdns_context_get_resolvconf(ctxt, &resolv))) {
Rf_error(getdns_get_errorstr_by_id(r));
}

return(std::string(resolv));

}

//' Retreive the value with which the context's upstream recursive servers and suffixes were initialized
//'
//' @param gctx gdns resolver context created with [gdns_resolver()]
//' @export
// [[Rcpp::export]]
StringVector gdns_get_tls_ca_path(SEXP gctx) {

check_is_xptr(gctx);

getdns_context *ctxt = (getdns_context *)R_ExternalPtrAddr(gctx);

if (gctx == NULL) return(R_NilValue);

getdns_return_t r;
const char *ca_path;

if ((r = getdns_context_get_tls_ca_path(ctxt, &ca_path))) {
Rf_error(getdns_get_errorstr_by_id(r));
}

return(ca_path ? std::string(ca_path) : CharacterVector());

}

//' Retreive the file location with CA certificates for verification purposes
//'
//' @param gctx gdns resolver context created with [gdns_resolver()]
//' @export
// [[Rcpp::export]]
StringVector gdns_get_tls_ca_file(SEXP gctx) {

check_is_xptr(gctx);

getdns_context *ctxt = (getdns_context *)R_ExternalPtrAddr(gctx);

if (gctx == NULL) return(R_NilValue);

getdns_return_t r;
const char *ca_file;

if ((r = getdns_context_get_tls_ca_path(ctxt, &ca_file))) {
Rf_error(getdns_get_errorstr_by_id(r));
}

return(ca_file ? std::string(ca_file) : CharacterVector());

}

//' Specify where the location for CA certificates for verification purposes are located
//'
//' @param gctx gdns resolver context created with [gdns_resolver()]
//' @param ca_path directory with Certificate Authority certificates
//' @export
// [[Rcpp::export]]
SEXP gdns_set_tls_ca_path(SEXP gctx, std::string ca_path) {

check_is_xptr(gctx);

getdns_context *ctxt = (getdns_context *)R_ExternalPtrAddr(gctx);

if (gctx == NULL) return(R_NilValue);

getdns_return_t r;

if ((r = getdns_context_set_tls_ca_path(ctxt, ca_path.c_str()))) {
Rf_error(getdns_get_errorstr_by_id(r));
}

return(gctx);

}

//' Specify the file with CA certificates for verification purposes
//'
//' @param gctx gdns resolver context created with [gdns_resolver()]
//' @param ca_file file with Certificate Authority certificates
//' @export
// [[Rcpp::export]]
SEXP gdns_set_tls_ca_file(SEXP gctx, std::string ca_file) {

check_is_xptr(gctx);

getdns_context *ctxt = (getdns_context *)R_ExternalPtrAddr(gctx);

if (gctx == NULL) return(R_NilValue);

getdns_return_t r;

if ((r = getdns_context_set_tls_ca_file(ctxt, ca_file.c_str()))) {
Rf_error(getdns_get_errorstr_by_id(r));
}

return(gctx);

}

+ 32
- 3
src/resolver.cpp View File

@@ -33,10 +33,10 @@ static void gctx_finalizer(SEXP ptr) {
R_ClearExternalPtr(ptr); /* not really needed */
}

//' Internal version of gdns_resolver
//' Internal version of gdns_context
//' @keywords internal
// [[Rcpp::export]]
SEXP int_gdns_resolver(std::vector< std::string > resolvers) {
SEXP int_gdns_context(std::vector< std::string > resolvers) {

bool ok = false;
SEXP ptr;
@@ -245,4 +245,33 @@ CharacterVector int_gdns_query(SEXP gctx, std::string name, uint16_t rr,

if (ok) return(wrap(out)); else return(CharacterVector());

}
}

//' Get the current resolution type setting
//'
//' @param gctx gdns resolver context created with [gdns_resolver()]
//' @export
//' @examples
//' x <- gdns_context()
//' gdns_get_resolution_type(x)
// [[Rcpp::export]]
CharacterVector gdns_get_resolution_type(SEXP gctx) {

check_is_xptr(gctx);

getdns_context *ctxt = (getdns_context *)R_ExternalPtrAddr(gctx);

if (gctx == NULL) return(CharacterVector());

getdns_return_t r;
getdns_resolution_t res_type;

if ((r = getdns_context_get_resolution_type(ctxt, &res_type))) {
Rf_error(getdns_get_errorstr_by_id(r));
}

std::string out = res_type == GETDNS_RESOLUTION_STUB ? "stub" : "recursive";

return(wrap(out));

}

+ 23
- 2
tests/testthat/test-clandnstine.R View File

@@ -3,12 +3,24 @@ test_that("basic wrapper works", {

expect_is(gdns_lib_version(), "character")

r <- gns_context()
r <- gdns_context()
expect_is(r, "gctx")

expect_is(gdns_lib_version(), "character")

expect_equal(gdns_get_resolution_type(r), "stub")

x <- gdns_get_address(r, "example.com")
expect_true(all(c("2606:2800:220:1:248:1893:25c8:1946", "93.184.216.34") %in% x))

r <- gdns_context(c("8.8.8.8", "1.1.1.1", "9.9.9.9"))
expect_error(gdns_set_timeout(x, 3000))

expect_is(gdns_update_resolvers(r, c("8.8.8.8", "1.1.1.1", "9.9.9.9")), "gctx")
expect_is(gdns_set_round_robin_upstreams(r, TRUE), "gctx")
expect_is(gdns_set_hosts(r, "/etc/hosts"), "gctx")
expect_is(gdns_set_timeout(r, 2000), "gctx")

expect_error(gdns_set_hosts(r, "/etc/hostssss"))

x <- gdns_get_address(r, "example.com")
expect_true(all(c("2606:2800:220:1:248:1893:25c8:1946", "93.184.216.34") %in% x))
@@ -22,4 +34,13 @@ test_that("basic wrapper works", {
expect_equal(x$canonical_name, "example.com.")
expect_true(grepl("spf", unlist(x$replies_tree$answer[[1]]$rdata$txt_strings)))

expect_equal(gdns_get_transports(r), "tls")
expect_is(gdns_set_transports(r, c("udp", "tls")), "gctx")
expect_equal(gdns_get_transports(r), c("udp", "tls"))

expect_true(
all(c("2606:2800:220:1:248:1893:25c8:1946", "93.184.216.34") %in%
gdns_get_address(r, "example.com"))
)

})

Loading…
Cancel
Save