mirror of
https://github.com/servalproject/serval-dna.git
synced 2025-02-21 01:42:18 +00:00
Pass keyring entry pin to running daemon and unlock identities
This commit is contained in:
parent
60e3f5a3fc
commit
ae7e120ed5
@ -1967,6 +1967,67 @@ int app_keyring_set_did(const struct cli_parsed *parsed, struct cli_context *con
|
||||
return 0;
|
||||
}
|
||||
|
||||
int app_id_pin(const struct cli_parsed *parsed, struct cli_context *context)
|
||||
{
|
||||
const char *pin;
|
||||
cli_arg(parsed, "entry-pin", &pin, NULL, "");
|
||||
int ret=1;
|
||||
struct mdp_header header={
|
||||
.remote.port=MDP_IDENTITY,
|
||||
};
|
||||
int mdp_sock = mdp_socket();
|
||||
set_nonblock(mdp_sock);
|
||||
|
||||
unsigned char payload[1200];
|
||||
struct mdp_identity_request *request = (struct mdp_identity_request *)payload;
|
||||
request->action=ACTION_UNLOCK;
|
||||
request->type=TYPE_PIN;
|
||||
int len = sizeof(struct mdp_identity_request);
|
||||
int pin_len = strlen(pin)+1;
|
||||
if (pin_len+len > sizeof(payload))
|
||||
return WHY("Supplied pin is too long");
|
||||
bcopy(pin, &payload[len], pin_len);
|
||||
len+=pin_len;
|
||||
|
||||
if (!mdp_send(mdp_sock, &header, payload, len)){
|
||||
WHY_perror("mdp_send");
|
||||
goto end;
|
||||
}
|
||||
|
||||
time_ms_t timeout=gettime_ms()+500;
|
||||
while(1){
|
||||
time_ms_t now = gettime_ms();
|
||||
if (now>timeout)
|
||||
break;
|
||||
int p=mdp_poll(mdp_sock, timeout - now);
|
||||
if (p<0){
|
||||
WHY_perror("mdp_poll");
|
||||
break;
|
||||
}
|
||||
if (p==0){
|
||||
WHYF("Timeout while waiting for response");
|
||||
break;
|
||||
}
|
||||
struct mdp_header rev_header;
|
||||
unsigned char payload[1600];
|
||||
ssize_t len = mdp_recv(mdp_sock, &rev_header, payload, sizeof(payload));
|
||||
if (len<0){
|
||||
WHY_perror("mdp_recv");
|
||||
continue;
|
||||
}
|
||||
if (rev_header.flags & MDP_FLAG_OK)
|
||||
ret=0;
|
||||
if (rev_header.flags & MDP_FLAG_ERROR){
|
||||
payload[len]=0;
|
||||
WHYF("%s",payload);
|
||||
}
|
||||
break;
|
||||
}
|
||||
end:
|
||||
mdp_close(mdp_sock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int app_id_self(const struct cli_parsed *parsed, struct cli_context *context)
|
||||
{
|
||||
int mdp_sockfd;
|
||||
@ -2521,6 +2582,8 @@ struct cli_schema command_line_options[]={
|
||||
"Set the DID for the specified SID (must supply PIN to unlock the SID record in the keyring)"},
|
||||
{app_id_self,{"id","self|peers|allpeers",NULL}, 0,
|
||||
"Return identity(s) as URIs of own node, or of known routable peers, or all known peers"},
|
||||
{app_id_pin, {"id", "enter", "pin", "<entry-pin>", NULL}, 0,
|
||||
"Unlock any pin protected identities and enable routing packets to them"},
|
||||
{app_route_print, {"route","print",NULL}, 0,
|
||||
"Print the routing table"},
|
||||
{app_network_scan, {"scan","[<address>]",NULL}, 0,
|
||||
|
108
mdp_client.c
108
mdp_client.c
@ -28,6 +28,94 @@
|
||||
#include "overlay_packet.h"
|
||||
#include "mdp_client.h"
|
||||
|
||||
int mdp_socket(void)
|
||||
{
|
||||
// for now use the same process for creating sockets
|
||||
return overlay_mdp_client_socket();
|
||||
}
|
||||
|
||||
int mdp_close(int socket)
|
||||
{
|
||||
// use the same process for closing sockets, though this will need to change once bind is implemented
|
||||
return overlay_mdp_client_close(socket);
|
||||
}
|
||||
|
||||
int mdp_send(int socket, const struct mdp_header *header, const unsigned char *payload, ssize_t len)
|
||||
{
|
||||
struct sockaddr_un addr;
|
||||
socklen_t addrlen;
|
||||
if (make_local_sockaddr(&addr, &addrlen, "mdp.2.socket") == -1)
|
||||
return -1;
|
||||
|
||||
struct iovec iov[]={
|
||||
{
|
||||
.iov_base = (void *)header,
|
||||
.iov_len = sizeof(struct mdp_header)
|
||||
},
|
||||
{
|
||||
.iov_base = (void *)payload,
|
||||
.iov_len = len
|
||||
}
|
||||
};
|
||||
|
||||
struct msghdr hdr={
|
||||
.msg_name=&addr,
|
||||
.msg_namelen=addrlen,
|
||||
.msg_iov=iov,
|
||||
.msg_iovlen=2,
|
||||
};
|
||||
|
||||
return sendmsg(socket, &hdr, 0);
|
||||
}
|
||||
|
||||
ssize_t mdp_recv(int socket, struct mdp_header *header, unsigned char *payload, ssize_t max_len)
|
||||
{
|
||||
/* Construct name of socket to receive from. */
|
||||
struct sockaddr_un mdp_addr;
|
||||
socklen_t mdp_addrlen;
|
||||
if (make_local_sockaddr(&mdp_addr, &mdp_addrlen, "mdp.2.socket") == -1)
|
||||
return -1;
|
||||
|
||||
struct sockaddr_un addr;
|
||||
struct iovec iov[]={
|
||||
{
|
||||
.iov_base = (void *)header,
|
||||
.iov_len = sizeof(struct mdp_header)
|
||||
},
|
||||
{
|
||||
.iov_base = (void *)payload,
|
||||
.iov_len = max_len
|
||||
}
|
||||
};
|
||||
|
||||
struct msghdr hdr={
|
||||
.msg_name=&addr,
|
||||
.msg_namelen=sizeof(struct sockaddr_un),
|
||||
.msg_iov=iov,
|
||||
.msg_iovlen=2,
|
||||
};
|
||||
|
||||
ssize_t len = recvmsg(socket, &hdr, 0);
|
||||
if (len<sizeof(struct mdp_header))
|
||||
return -1;
|
||||
|
||||
// double check that the incoming address matches the servald daemon
|
||||
if (cmp_sockaddr((struct sockaddr *)&addr, hdr.msg_namelen, (struct sockaddr *)&mdp_addr, mdp_addrlen) != 0
|
||||
&& ( addr.sun_family != AF_UNIX
|
||||
|| real_sockaddr(&addr, hdr.msg_namelen, &addr, &hdr.msg_namelen) <= 0
|
||||
|| cmp_sockaddr((struct sockaddr *)&addr, hdr.msg_namelen, (struct sockaddr *)&mdp_addr, mdp_addrlen) != 0
|
||||
)
|
||||
)
|
||||
return -1;
|
||||
|
||||
return len - sizeof(struct mdp_header);
|
||||
}
|
||||
|
||||
int mdp_poll(int socket, time_ms_t timeout_ms)
|
||||
{
|
||||
return overlay_mdp_client_poll(socket, timeout_ms);
|
||||
}
|
||||
|
||||
int overlay_mdp_send(int mdp_sockfd, overlay_mdp_frame *mdp, int flags, int timeout_ms)
|
||||
{
|
||||
if (mdp_sockfd == -1)
|
||||
@ -128,21 +216,17 @@ int overlay_mdp_client_close(int mdp_sockfd)
|
||||
int overlay_mdp_client_poll(int mdp_sockfd, time_ms_t timeout_ms)
|
||||
{
|
||||
fd_set r;
|
||||
int ret;
|
||||
FD_ZERO(&r);
|
||||
FD_SET(mdp_sockfd, &r);
|
||||
if (timeout_ms<0) timeout_ms=0;
|
||||
|
||||
struct timeval tv;
|
||||
|
||||
if (timeout_ms>=0) {
|
||||
tv.tv_sec=timeout_ms/1000;
|
||||
tv.tv_usec=(timeout_ms%1000)*1000;
|
||||
ret=select(mdp_sockfd+1,&r,NULL,&r,&tv);
|
||||
}
|
||||
else
|
||||
ret=select(mdp_sockfd+1,&r,NULL,&r,NULL);
|
||||
return ret;
|
||||
struct pollfd fds[]={
|
||||
{
|
||||
.fd = mdp_sockfd,
|
||||
.events = POLLIN|POLLERR,
|
||||
}
|
||||
};
|
||||
return poll(fds, 1, timeout_ms);
|
||||
}
|
||||
|
||||
int overlay_mdp_recv(int mdp_sockfd, overlay_mdp_frame *mdp, int port, int *ttl)
|
||||
@ -165,7 +249,7 @@ int overlay_mdp_recv(int mdp_sockfd, overlay_mdp_frame *mdp, int port, int *ttl)
|
||||
return -1; // no packet received
|
||||
|
||||
// If the received address overflowed the buffer, then it cannot have come from the server, whose
|
||||
// address always fits within a struct sockaddr_un.
|
||||
// address must always fit within a struct sockaddr_un.
|
||||
if (recvaddrlen > sizeof recvaddr)
|
||||
return WHY("reply did not come from server: address overrun");
|
||||
|
||||
|
44
mdp_client.h
44
mdp_client.h
@ -21,6 +21,43 @@
|
||||
|
||||
#include "serval.h"
|
||||
|
||||
// define 3rd party mdp API without any structure padding
|
||||
#pragma pack(push, 1)
|
||||
|
||||
struct mdp_sockaddr {
|
||||
sid_t sid;
|
||||
uint32_t port;
|
||||
};
|
||||
|
||||
#define MDP_FLAG_NO_CRYPT (1<<0)
|
||||
#define MDP_FLAG_NO_SIGN (1<<1)
|
||||
#define MDP_FLAG_BIND_ALL (1<<2)
|
||||
#define MDP_FLAG_OK (1<<3)
|
||||
#define MDP_FLAG_ERROR (1<<4)
|
||||
|
||||
struct mdp_header {
|
||||
struct mdp_sockaddr local;
|
||||
struct mdp_sockaddr remote;
|
||||
uint8_t flags;
|
||||
uint8_t qos;
|
||||
uint8_t ttl;
|
||||
};
|
||||
|
||||
#define TYPE_SID 1
|
||||
#define TYPE_PIN 2
|
||||
#define ACTION_LOCK 1
|
||||
#define ACTION_UNLOCK 2
|
||||
|
||||
struct mdp_identity_request{
|
||||
uint8_t action;
|
||||
uint8_t type;
|
||||
// followed by a list of SID's or NULL terminated entry pins for the remainder of the payload
|
||||
};
|
||||
|
||||
#define MDP_IDENTITY 1
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
struct overlay_route_record{
|
||||
unsigned char sid[SID_SIZE];
|
||||
char interface_name[256];
|
||||
@ -32,6 +69,13 @@ struct overlay_mdp_scan{
|
||||
struct in_addr addr;
|
||||
};
|
||||
|
||||
/* V2 interface */
|
||||
int mdp_socket(void);
|
||||
int mdp_close(int socket);
|
||||
int mdp_send(int socket, const struct mdp_header *header, const unsigned char *payload, ssize_t len);
|
||||
ssize_t mdp_recv(int socket, struct mdp_header *header, unsigned char *payload, ssize_t max_len);
|
||||
int mdp_poll(int socket, time_ms_t timeout_ms);
|
||||
|
||||
/* Client-side MDP function */
|
||||
int overlay_mdp_client_socket(void);
|
||||
int overlay_mdp_client_close(int mdp_sockfd);
|
||||
|
183
overlay_mdp.c
183
overlay_mdp.c
@ -30,8 +30,22 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#include "mdp_client.h"
|
||||
#include "crypto.h"
|
||||
|
||||
static void overlay_mdp_poll(struct sched_ent *alarm);
|
||||
static void mdp_poll2(struct sched_ent *alarm);
|
||||
|
||||
static struct profile_total mdp_stats = { .name="overlay_mdp_poll" };
|
||||
static struct sched_ent mdp_sock = STRUCT_SCHED_ENT_UNUSED;
|
||||
static struct sched_ent mdp_sock = {
|
||||
.function = overlay_mdp_poll,
|
||||
.stats = &mdp_stats,
|
||||
.poll.fd = -1,
|
||||
};
|
||||
|
||||
static struct profile_total mdp_stats2 = { .name="mdp_poll2" };
|
||||
static struct sched_ent mdp_sock2 = {
|
||||
.function = mdp_poll2,
|
||||
.stats = &mdp_stats2,
|
||||
.poll.fd = -1,
|
||||
};
|
||||
|
||||
static int overlay_saw_mdp_frame(struct overlay_frame *frame, overlay_mdp_frame *mdp, time_ms_t now);
|
||||
|
||||
@ -60,37 +74,43 @@ static void overlay_mdp_clean_socket_files()
|
||||
closedir(dir);
|
||||
}
|
||||
|
||||
int overlay_mdp_setup_sockets()
|
||||
static int mdp_bind_socket(const char *name)
|
||||
{
|
||||
struct sockaddr_un addr;
|
||||
socklen_t addrlen;
|
||||
int sock;
|
||||
|
||||
if (make_local_sockaddr(&addr, &addrlen, "%s", name) == -1)
|
||||
return -1;
|
||||
if ((sock = esocket(AF_UNIX, SOCK_DGRAM, 0)) == -1)
|
||||
return -1;
|
||||
if (socket_set_reuseaddr(sock, 1) == -1)
|
||||
WARN("Could not set socket to reuse addresses");
|
||||
if (socket_bind(sock, (struct sockaddr *)&addr, addrlen) == -1) {
|
||||
close(sock);
|
||||
return -1;
|
||||
}
|
||||
socket_set_rcvbufsize(sock, 64 * 1024);
|
||||
|
||||
INFOF("Socket %s: fd=%d %s", name, sock, alloca_sockaddr(&addr, addrlen));
|
||||
return sock;
|
||||
}
|
||||
|
||||
int overlay_mdp_setup_sockets()
|
||||
{
|
||||
/* Delete stale socket files from instance directory. */
|
||||
overlay_mdp_clean_socket_files();
|
||||
|
||||
if (mdp_sock.poll.fd == -1) {
|
||||
if (make_local_sockaddr(&addr, &addrlen, "mdp.socket") == -1)
|
||||
return -1;
|
||||
if ((mdp_sock.poll.fd = esocket(AF_UNIX, SOCK_DGRAM, 0)) == -1)
|
||||
return -1;
|
||||
if (socket_set_reuseaddr(mdp_sock.poll.fd, 1) == -1)
|
||||
WARN("Could not set socket to reuse addresses");
|
||||
if (socket_bind(mdp_sock.poll.fd, (struct sockaddr *)&addr, addrlen) == -1) {
|
||||
close(mdp_sock.poll.fd);
|
||||
mdp_sock.poll.fd = -1;
|
||||
return -1;
|
||||
}
|
||||
socket_set_rcvbufsize(mdp_sock.poll.fd, 64 * 1024);
|
||||
#if 0
|
||||
int buffer_size = 64 * 1024;
|
||||
if (setsockopt(mdp_sock.poll.fd, SOL_SOCKET, SO_SNDBUF, &buffer_size, sizeof(buffer_size)) == -1)
|
||||
WARNF_perror("setsockopt(%d,SOL_SOCKET,SO_SNDBUF,&%d,%d)", mdp_sock.poll.fd, buffer_size, sizeof buffer_size);
|
||||
#endif
|
||||
mdp_sock.function = overlay_mdp_poll;
|
||||
mdp_sock.stats = &mdp_stats;
|
||||
mdp_sock.poll.fd = mdp_bind_socket("mdp.socket");
|
||||
mdp_sock.poll.events = POLLIN;
|
||||
watch(&mdp_sock);
|
||||
INFOF("MDP socket: fd=%d %s", mdp_sock.poll.fd, alloca_sockaddr(&addr, addrlen));
|
||||
}
|
||||
|
||||
if (mdp_sock2.poll.fd == -1) {
|
||||
mdp_sock2.poll.fd = mdp_bind_socket("mdp.2.socket");
|
||||
mdp_sock2.poll.events = POLLIN;
|
||||
watch(&mdp_sock2);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@ -914,7 +934,124 @@ static void overlay_mdp_scan(struct sched_ent *alarm)
|
||||
}
|
||||
}
|
||||
|
||||
void overlay_mdp_poll(struct sched_ent *alarm)
|
||||
struct mdp_client{
|
||||
struct sockaddr_un *addr;
|
||||
socklen_t addrlen;
|
||||
};
|
||||
|
||||
static int mdp_reply2(const struct mdp_client *client, const struct mdp_header *header,
|
||||
int flags, const unsigned char *payload, int payload_len)
|
||||
{
|
||||
struct mdp_header response_header;
|
||||
bcopy(header, &response_header, sizeof(response_header));
|
||||
response_header.flags = flags;
|
||||
|
||||
struct iovec iov[]={
|
||||
{
|
||||
.iov_base = (void *)&response_header,
|
||||
.iov_len = sizeof(struct mdp_header)
|
||||
},
|
||||
{
|
||||
.iov_base = (void *)payload,
|
||||
.iov_len = payload_len
|
||||
}
|
||||
};
|
||||
|
||||
struct msghdr hdr={
|
||||
.msg_name=client->addr,
|
||||
.msg_namelen=client->addrlen,
|
||||
.msg_iov=iov,
|
||||
.msg_iovlen=2,
|
||||
};
|
||||
|
||||
if (config.debug.mdprequests)
|
||||
DEBUGF("Replying to %s with code %d", alloca_sockaddr(client->addr, client->addrlen), flags);
|
||||
return sendmsg(mdp_sock2.poll.fd, &hdr, 0);
|
||||
}
|
||||
|
||||
#define mdp_reply_error(A,B,C) mdp_reply2(A,B,MDP_FLAG_ERROR,(const unsigned char *)C,strlen(C))
|
||||
#define mdp_reply_ok(A,B) mdp_reply2(A,B,MDP_FLAG_OK,NULL,0)
|
||||
|
||||
static int mdp_process_identity_request(struct mdp_client *client, struct mdp_header *header,
|
||||
const unsigned char *payload, int payload_len)
|
||||
{
|
||||
if (payload_len<sizeof(struct mdp_identity_request)){
|
||||
mdp_reply_error(client, header, "Request too short");
|
||||
return -1;
|
||||
}
|
||||
struct mdp_identity_request *request = (struct mdp_identity_request *)payload;
|
||||
payload += sizeof(struct mdp_identity_request);
|
||||
payload_len -= sizeof(struct mdp_identity_request);
|
||||
|
||||
switch(request->action){
|
||||
case ACTION_UNLOCK:
|
||||
{
|
||||
if (request->type!=TYPE_PIN){
|
||||
mdp_reply_error(client, header, "Unknown request type");
|
||||
return -1;
|
||||
}
|
||||
int unlock_count=0;
|
||||
const char *pin = (char *)payload;
|
||||
int ofs=0;
|
||||
while(ofs < payload_len){
|
||||
if (!payload[ofs++]){
|
||||
unlock_count += keyring_enter_pin(keyring, pin);
|
||||
pin=(char *)&payload[ofs++];
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
mdp_reply_error(client, header, "Unknown request action");
|
||||
return -1;
|
||||
}
|
||||
mdp_reply_ok(client, header);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mdp_poll2(struct sched_ent *alarm)
|
||||
{
|
||||
if (alarm->poll.revents & POLLIN) {
|
||||
unsigned char buffer[1600];
|
||||
struct sockaddr_storage addr;
|
||||
struct mdp_client client={
|
||||
.addr = (struct sockaddr_un *)&addr,
|
||||
.addrlen = sizeof(addr)
|
||||
};
|
||||
int ttl=-1;
|
||||
|
||||
ssize_t len = recvwithttl(alarm->poll.fd, buffer, sizeof(buffer), &ttl, (struct sockaddr *)&addr, &client.addrlen);
|
||||
|
||||
if (len<=sizeof(struct mdp_header)){
|
||||
WHYF("Expected length %d, got %d from %s", (int)sizeof(struct mdp_header), (int)len, alloca_sockaddr(client.addr, client.addrlen));
|
||||
return;
|
||||
}
|
||||
|
||||
struct mdp_header *header = (struct mdp_header *)buffer;
|
||||
|
||||
unsigned char *payload = &buffer[sizeof(struct mdp_header)];
|
||||
int payload_len = len - sizeof(struct mdp_header);
|
||||
|
||||
if (is_sid_any(header->remote.sid.binary)){
|
||||
// process local commands
|
||||
switch(header->remote.port){
|
||||
case MDP_IDENTITY:
|
||||
if (config.debug.mdprequests)
|
||||
DEBUGF("Processing MDP_IDENTITY from %s", alloca_sockaddr(client.addr, client.addrlen));
|
||||
mdp_process_identity_request(&client, header, payload, payload_len);
|
||||
break;
|
||||
default:
|
||||
mdp_reply_error(&client, header, "Unknown port number");
|
||||
break;
|
||||
}
|
||||
}else{
|
||||
// TODO transmit packet
|
||||
mdp_reply_error(&client, header, "Transmitting packets is not yet supported");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void overlay_mdp_poll(struct sched_ent *alarm)
|
||||
{
|
||||
if (alarm->poll.revents & POLLIN) {
|
||||
unsigned char buffer[16384];
|
||||
|
1
serval.h
1
serval.h
@ -831,7 +831,6 @@ int overlay_packetradio_tx_packet(struct overlay_frame *frame);
|
||||
void overlay_dummy_poll(struct sched_ent *alarm);
|
||||
void server_config_reload(struct sched_ent *alarm);
|
||||
void server_shutdown_check(struct sched_ent *alarm);
|
||||
void overlay_mdp_poll(struct sched_ent *alarm);
|
||||
int overlay_mdp_try_interal_services(struct overlay_frame *frame, overlay_mdp_frame *mdp);
|
||||
int overlay_send_probe(struct subscriber *peer, struct network_destination *destination, int queue);
|
||||
int overlay_send_stun_request(struct subscriber *server, struct subscriber *request);
|
||||
|
@ -206,6 +206,42 @@ teardown_KeyringKeyringPinServer() {
|
||||
report_servald_server
|
||||
}
|
||||
|
||||
doc_KeyringEntryPinServer="Start daemon and unlock identities"
|
||||
setup_KeyringEntryPinServer() {
|
||||
setup
|
||||
executeOk_servald config set debug.mdprequests on
|
||||
create_single_identity
|
||||
executeOk_servald keyring add 'one'
|
||||
extract_stdout_keyvalue ONE sid "$rexp_sid"
|
||||
executeOk_servald keyring add 'two'
|
||||
extract_stdout_keyvalue TWOA sid "$rexp_sid"
|
||||
executeOk_servald keyring add 'two'
|
||||
extract_stdout_keyvalue TWOB sid "$rexp_sid"
|
||||
start_servald_server
|
||||
}
|
||||
test_KeyringEntryPinServer() {
|
||||
executeOk_servald id self
|
||||
assertStdoutLineCount == 1
|
||||
assertStdoutGrep --fixed-strings "$SIDA"
|
||||
executeOk_servald id enter pin 'one'
|
||||
executeOk_servald id self
|
||||
assertStdoutLineCount == 2
|
||||
assertStdoutGrep --fixed-strings "$SIDA"
|
||||
assertStdoutGrep --fixed-strings "$ONE"
|
||||
executeOk_servald id enter pin 'two'
|
||||
executeOk_servald id self
|
||||
assertStdoutLineCount == 4
|
||||
assertStdoutGrep --fixed-strings "$SIDA"
|
||||
assertStdoutGrep --fixed-strings "$ONE"
|
||||
assertStdoutGrep --fixed-strings "$TWOA"
|
||||
assertStdoutGrep --fixed-strings "$TWOB"
|
||||
}
|
||||
teardown_KeyringEntryPinServer() {
|
||||
kill_all_servald_processes
|
||||
assert_no_servald_processes
|
||||
report_servald_server
|
||||
}
|
||||
|
||||
doc_Load="Load keyring entries from a keyring dump"
|
||||
setup_Load() {
|
||||
setup_servald
|
||||
|
Loading…
x
Reference in New Issue
Block a user