blob: c02e27119288362a5ee11411a5ba07afd374f63a [file] [log] [blame]
/* srp-ioloop.c
*
* Copyright (c) 2019 Apple Computer, Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* srp host API implementation for Posix using ioloop primitives.
*/
#include <stdio.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <dns_sd.h>
#include <errno.h>
#include <fcntl.h>
#include "srp.h"
#include "srp-api.h"
#include "dns-msg.h"
#include "srp-crypto.h"
#include "ioloop.h"
bool
srp_load_file_data(void *host_context, const char *filename, uint8_t *buffer, uint16_t *length, uint16_t buffer_size)
{
off_t flen;
ssize_t len;
int file;
(void)host_context;
file = open(filename, O_RDONLY);
if (file < 0) {
ERROR("srp_load_file_data: %s: open: %s", filename, strerror(errno));
return false;
}
// Get the length of the file.
flen = lseek(file, 0, SEEK_END);
lseek(file, 0, SEEK_SET);
if (flen > buffer_size) {
ERROR("srp_load_file_data: %s: lseek: %s", filename, strerror(errno));
close(file);
return false;
}
len = read(file, buffer, (size_t)flen); // Note: flen always positive, no loss of precision.
if (len < 0 || len != flen) {
if (len < 0) {
ERROR("srp_load_file_data: %s: read: %s", filename, strerror(errno));
} else {
ERROR("srp_load_file_data: %s: short read %d out of %d", filename, (int)len, (int)flen);
}
close(file);
return false;
}
close(file);
*length = (uint16_t)len;
return true;
}
bool
srp_store_file_data(void *host_context, const char *filename, uint8_t *buffer, uint16_t length)
{
ssize_t len;
int file;
(void)host_context;
file = open(filename, O_WRONLY | O_CREAT, 0600);
if (file < 0) {
ERROR("srp_store_file_data: %s: %s", filename, strerror(errno));
return false;
}
len = write(file, buffer, length);
if (len < 0 || len != length) {
if (len < 0) {
ERROR("srp_store_file_data: " PUB_S_SRP ": " PUB_S_SRP, filename, strerror(errno));
} else {
ERROR("srp_store_file_data: short write %d out of %d on file " PUB_S_SRP, (int)len, (int)length, filename);
}
unlink(filename);
close(file);
return false;
}
close(file);
return true;
}
bool
srp_get_last_server(uint16_t *NONNULL rrtype, uint8_t *NONNULL rdata, uint16_t rdlim,
uint8_t *NONNULL port, void *NULLABLE host_context)
{
uint8_t buffer[22];
unsigned offset = 0;
uint16_t length;
uint16_t rdlength;
if (!srp_load_file_data(host_context, "/var/run/srp-last-server", buffer, &length, sizeof(buffer))) {
return false;
}
if (length < 10) { // rrtype + rdlength + ipv4 address + port
ERROR("srp_get_last_server: stored server data is too short: %d", length);
return false;
}
*rrtype = (((uint16_t)buffer[offset]) << 8) | buffer[offset + 1];
offset += 2;
rdlength = (((uint16_t)buffer[offset]) << 8) | buffer[offset + 1];
offset += 2;
if ((*rrtype == dns_rrtype_a && rdlength != 4) || (*rrtype == dns_rrtype_aaaa && rdlength != 16)) {
ERROR("srp_get_last_server: invalid rdlength %d for %s record",
rdlength, *rrtype == dns_rrtype_a ? "A" : "AAAA");
return false;
}
if (length < rdlength + 6) { // rrtype + rdlength + address + port
ERROR("srp_get_last_server: stored server data length %d is too short", length);
return false;
}
if (rdlength > rdlim) {
ERROR("srp_get_last_server: no space for %s data in provided buffer size %d",
*rrtype == dns_rrtype_a ? "A" : "AAAA", rdlim);
return false;
}
memcpy(rdata, &buffer[offset], rdlength);
offset += rdlength;
memcpy(port, &buffer[offset], 2);
return true;
}
bool
srp_save_last_server(uint16_t rrtype, uint8_t *NONNULL rdata, uint16_t rdlength,
uint8_t *NONNULL port, void *NULLABLE host_context)
{
dns_towire_state_t towire;
uint8_t buffer[24];
size_t length;
memset(&towire, 0, sizeof(towire));
towire.p = buffer;
towire.lim = towire.p + sizeof(buffer);
if (rdlength != 4 && rdlength != 16) {
ERROR("srp_save_last_server: invalid IP address length %d", rdlength);
return false;
}
dns_u16_to_wire(&towire, rrtype);
dns_u16_to_wire(&towire, rdlength);
dns_rdata_raw_data_to_wire(&towire, rdata, rdlength);
dns_rdata_raw_data_to_wire(&towire, port, 2);
if (towire.error) {
ERROR("srp_save_last_server: " PUB_S_SRP " at %d (%p:%p:%p) while constructing output buffer",
strerror(towire.error), towire.line, towire.p, towire.lim, buffer);
return false;
}
length = towire.p - buffer;
if (!srp_store_file_data(host_context, "/var/run/srp-last-server", buffer, length)) {
return false;
}
return true;
}
#ifdef SRP_CRYPTO_MBEDTLS
int
srp_load_key_data(void *host_context, const char *key_name, uint8_t *buffer, uint16_t *length, uint16_t buffer_size)
{
if (srp_load_file_data(host_context, key_name, buffer, length, buffer_size)) {
return kDNSServiceErr_NoError;
}
return kDNSServiceErr_NoSuchKey;
}
int
srp_store_key_data(void *host_context, const char *key_name, uint8_t *buffer, uint16_t length)
{
if (!srp_store_file_data(host_context, key_name, buffer, length)) {
return kDNSServiceErr_Unknown;
}
return kDNSServiceErr_NoError;
}
int
srp_remove_key_file(void *host_context, const char *key_name)
{
if (unlink(key_name) < 0) {
return kDNSServiceErr_Unknown;
}
return kDNSServiceErr_NoError;
}
#endif // SRP_CRYPTO_MBEDTLS_INTERNAL