916 lines
32 KiB
C
916 lines
32 KiB
C
/*
|
|
Copyright (c) 2014 Anton Titov.
|
|
|
|
Copyright (c) 2014 pCloud Ltd. All rights reserved.
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
modification, are permitted provided that the following conditions
|
|
are met: Redistributions of source code must retain the above
|
|
copyright notice, this list of conditions and the following
|
|
disclaimer. Redistributions in binary form must reproduce the
|
|
above copyright notice, this list of conditions and the following
|
|
disclaimer in the documentation and/or other materials provided
|
|
with the distribution. Neither the name of pCloud Ltd nor the
|
|
names of its contributors may be used to endorse or promote
|
|
products derived from this software without specific prior written
|
|
permission.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
|
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL pCloud
|
|
Ltd BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
|
OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
|
USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
|
DAMAGE.
|
|
*/
|
|
|
|
#include <errno.h>
|
|
#include <pthread.h>
|
|
#include <string.h>
|
|
|
|
#include "papi.h"
|
|
#include "pcrypto.h"
|
|
#include "pdownload.h"
|
|
#include "pfile.h"
|
|
#include "pfoldersync.h"
|
|
#include "plibs.h"
|
|
#include "pmem.h"
|
|
#include "pnetlibs.h"
|
|
#include "pp2p.h"
|
|
#include "prun.h"
|
|
#include "psettings.h"
|
|
#include "psql.h"
|
|
#include "pssl.h"
|
|
#include "pstatus.h"
|
|
#include "psys.h"
|
|
#include "ptimer.h"
|
|
#include "putil.h"
|
|
|
|
#define P2P_ENCTYPE_RSA_AES 0
|
|
|
|
typedef uint32_t packet_type_t;
|
|
typedef uint32_t packet_id_t;
|
|
typedef uint32_t packet_resp_t;
|
|
|
|
typedef PSYNC_PACKED_STRUCT {
|
|
packet_type_t type;
|
|
unsigned char hashstart[4];
|
|
uint64_t filesize;
|
|
unsigned char rand[PSYNC_HASH_BLOCK_SIZE - PSYNC_HASH_DIGEST_HEXLEN];
|
|
unsigned char genhash[PSYNC_HASH_DIGEST_HEXLEN];
|
|
unsigned char computername[PSYNC_HASH_DIGEST_HEXLEN];
|
|
}
|
|
packet_check;
|
|
|
|
typedef PSYNC_PACKED_STRUCT {
|
|
packet_resp_t type;
|
|
uint32_t port;
|
|
unsigned char rand[PSYNC_HASH_BLOCK_SIZE - PSYNC_HASH_DIGEST_HEXLEN];
|
|
unsigned char genhash[PSYNC_HASH_DIGEST_HEXLEN];
|
|
}
|
|
packet_check_resp;
|
|
|
|
typedef PSYNC_PACKED_STRUCT {
|
|
packet_type_t type;
|
|
unsigned char hashstart[4];
|
|
uint64_t filesize;
|
|
uint32_t keylen;
|
|
uint32_t tokenlen;
|
|
unsigned char rand[PSYNC_HASH_BLOCK_SIZE - PSYNC_HASH_DIGEST_HEXLEN];
|
|
unsigned char genhash[PSYNC_HASH_DIGEST_HEXLEN];
|
|
unsigned char computername[PSYNC_HASH_DIGEST_HEXLEN];
|
|
}
|
|
packet_get;
|
|
|
|
static const int on = 1;
|
|
|
|
static const size_t min_packet_size[] = {
|
|
#define P2P_WAKE 0
|
|
sizeof(packet_type_t),
|
|
#define P2P_CHECK 1
|
|
sizeof(packet_check),
|
|
#define P2P_GET 2
|
|
sizeof(packet_get)};
|
|
|
|
#define P2P_RESP_NOPE 0
|
|
#define P2P_RESP_HAVEIT 1
|
|
#define P2P_RESP_WAIT 2
|
|
|
|
static pthread_mutex_t p2pmutex = PTHREAD_MUTEX_INITIALIZER;
|
|
|
|
static int udpsock;
|
|
static int files_serving = 0;
|
|
static int running = 0;
|
|
static int tcpport;
|
|
|
|
static char computername[PSYNC_HASH_DIGEST_HEXLEN];
|
|
|
|
static const uint32_t requiredstatuses[] = {
|
|
PSTATUS_COMBINE(PSTATUS_TYPE_AUTH, PSTATUS_AUTH_PROVIDED),
|
|
PSTATUS_COMBINE(PSTATUS_TYPE_RUN, PSTATUS_RUN_RUN),
|
|
PSTATUS_COMBINE(PSTATUS_TYPE_ONLINE, PSTATUS_ONLINE_ONLINE)};
|
|
|
|
static struct sockaddr_storage paddr;
|
|
static socklen_t paddrlen;
|
|
|
|
static psync_rsa_publickey_t pubkey = PSYNC_INVALID_RSA;
|
|
static psync_rsa_privatekey_t privkey = PSYNC_INVALID_RSA;
|
|
static psync_binary_rsa_key_t pubkeybin = PSYNC_INVALID_BIN_RSA;
|
|
|
|
PSYNC_PURE static const char *get_addr(void *addr) {
|
|
if (((struct sockaddr_in *)addr)->sin_family == AF_INET)
|
|
return inet_ntoa(((struct sockaddr_in *)addr)->sin_addr);
|
|
else {
|
|
static char buff[80];
|
|
return inet_ntop(AF_INET6, &((struct sockaddr_in6 *)addr)->sin6_addr, buff,
|
|
sizeof(buff));
|
|
}
|
|
}
|
|
|
|
PSYNC_PURE static const char *get_peer_addr() {
|
|
if (paddr.ss_family == AF_INET)
|
|
return inet_ntoa(((struct sockaddr_in *)&paddr)->sin_addr);
|
|
else {
|
|
static char buff[80];
|
|
return inet_ntop(AF_INET6, &((struct sockaddr_in6 *)&paddr)->sin6_addr,
|
|
buff, sizeof(buff));
|
|
}
|
|
}
|
|
|
|
static psync_fileid_t has_file(const unsigned char *hashstart,
|
|
const unsigned char *genhash,
|
|
const unsigned char *rand,
|
|
uint64_t filesize,
|
|
unsigned char *realhash) {
|
|
psync_sql_res *res;
|
|
psync_variant_row row;
|
|
psync_fileid_t ret;
|
|
unsigned char hashsource[PSYNC_HASH_BLOCK_SIZE],
|
|
hashbin[PSYNC_HASH_DIGEST_LEN], hashhex[PSYNC_HASH_DIGEST_HEXLEN];
|
|
char like[PSYNC_P2P_HEXHASH_BYTES + 1];
|
|
memcpy(like, hashstart, PSYNC_P2P_HEXHASH_BYTES);
|
|
like[PSYNC_P2P_HEXHASH_BYTES] = '%';
|
|
memcpy(hashsource + PSYNC_HASH_DIGEST_HEXLEN, rand,
|
|
PSYNC_HASH_BLOCK_SIZE - PSYNC_HASH_DIGEST_HEXLEN);
|
|
res = psql_query_rdlock(
|
|
"SELECT id, checksum FROM localfile WHERE checksum LIKE ? AND size=?");
|
|
psql_bind_lstr(res, 1, like, PSYNC_P2P_HEXHASH_BYTES + 1);
|
|
psql_bind_uint(res, 2, filesize);
|
|
while ((row = psql_fetch(res))) {
|
|
pdbg_assertw(row[1].type == PSYNC_TSTRING &&
|
|
row[1].length == PSYNC_HASH_DIGEST_HEXLEN);
|
|
memcpy(hashsource, row[1].str, PSYNC_HASH_DIGEST_HEXLEN);
|
|
psync_hash(hashsource, PSYNC_HASH_BLOCK_SIZE, hashbin);
|
|
psync_binhex(hashhex, hashbin, PSYNC_HASH_DIGEST_LEN);
|
|
if (!memcmp(hashhex, genhash, PSYNC_HASH_DIGEST_HEXLEN)) {
|
|
if (realhash)
|
|
memcpy(realhash, row[1].str, PSYNC_HASH_DIGEST_HEXLEN);
|
|
ret = psync_get_number(row[0]);
|
|
psql_free(res);
|
|
return ret;
|
|
}
|
|
}
|
|
psql_free(res);
|
|
return 0;
|
|
}
|
|
|
|
static int is_downloading(const unsigned char *hashstart,
|
|
const unsigned char *genhash,
|
|
const unsigned char *rand,
|
|
uint64_t filesize,
|
|
unsigned char *realhash) {
|
|
download_hashes_t *hashes;
|
|
unsigned char hashsource[PSYNC_HASH_BLOCK_SIZE],
|
|
hashbin[PSYNC_HASH_DIGEST_LEN], hashhex[PSYNC_HASH_DIGEST_HEXLEN];
|
|
size_t i;
|
|
hashes = pdownload_get_hashes();
|
|
for (i = 0; i < hashes->hashcnt; i++) {
|
|
if (memcmp(hashstart, hashes->hashes[i], PSYNC_P2P_HEXHASH_BYTES))
|
|
continue;
|
|
memcpy(hashsource, hashes->hashes[i], PSYNC_HASH_DIGEST_HEXLEN);
|
|
memcpy(hashsource + PSYNC_HASH_DIGEST_HEXLEN, rand,
|
|
PSYNC_HASH_BLOCK_SIZE - PSYNC_HASH_DIGEST_HEXLEN);
|
|
psync_hash(hashsource, PSYNC_HASH_BLOCK_SIZE, hashbin);
|
|
psync_binhex(hashhex, hashbin, PSYNC_HASH_DIGEST_LEN);
|
|
if (!memcmp(hashhex, genhash, PSYNC_HASH_DIGEST_HEXLEN)) {
|
|
if (realhash)
|
|
memcpy(realhash, hashsource, PSYNC_HASH_DIGEST_HEXLEN);
|
|
pmem_free(PMEM_SUBSYS_OTHER, hashes);
|
|
return 1;
|
|
}
|
|
}
|
|
pmem_free(PMEM_SUBSYS_OTHER, hashes);
|
|
return 0;
|
|
}
|
|
|
|
static void psync_p2p_check(const packet_check *packet) {
|
|
unsigned char hashhex[PSYNC_HASH_DIGEST_HEXLEN],
|
|
hashsource[PSYNC_HASH_BLOCK_SIZE], hashbin[PSYNC_HASH_DIGEST_LEN];
|
|
packet_check_resp resp;
|
|
if (!memcmp(packet->computername, computername, PSYNC_HASH_DIGEST_HEXLEN))
|
|
return;
|
|
if (has_file(packet->hashstart, packet->genhash, packet->rand,
|
|
packet->filesize, hashhex))
|
|
resp.type = P2P_RESP_HAVEIT;
|
|
else if (is_downloading(packet->hashstart, packet->genhash,
|
|
packet->rand, packet->filesize, hashhex))
|
|
resp.type = P2P_RESP_WAIT;
|
|
else
|
|
return;
|
|
resp.port = tcpport;
|
|
pssl_rand_strong(resp.rand, sizeof(resp.rand));
|
|
memcpy(hashsource, hashhex, PSYNC_HASH_DIGEST_HEXLEN);
|
|
memcpy(hashsource + PSYNC_HASH_DIGEST_HEXLEN, resp.rand, sizeof(resp.rand));
|
|
psync_hash(hashsource, PSYNC_HASH_BLOCK_SIZE, hashbin);
|
|
psync_binhex(resp.genhash, hashbin, PSYNC_HASH_DIGEST_LEN);
|
|
pdbg_logf(D_NOTICE,
|
|
"replying with %u to a check from %s, looking for %." NTO_STR(
|
|
PSYNC_HASH_DIGEST_HEXLEN) "s",
|
|
(unsigned int)resp.type, get_peer_addr(), hashhex);
|
|
if (files_serving)
|
|
psys_sleep_milliseconds(files_serving * 10);
|
|
if (resp.type == P2P_RESP_WAIT)
|
|
psys_sleep_milliseconds(PSYNC_P2P_INITIAL_TIMEOUT / 4);
|
|
if (!sendto(udpsock, (const char *)&resp, sizeof(resp), 0,
|
|
(const struct sockaddr *)&paddr, paddrlen))
|
|
pdbg_logf(D_WARNING, "sendto to %s failed", get_peer_addr());
|
|
}
|
|
|
|
static void psync_p2p_process_packet(const char *packet, size_t plen) {
|
|
packet_type_t type;
|
|
if (unlikely(plen < sizeof(packet_type_t)))
|
|
return;
|
|
type = *((packet_type_t *)packet);
|
|
if (type >= ARRAY_SIZE(min_packet_size) || min_packet_size[type] > plen)
|
|
return;
|
|
pdbg_logf(D_NOTICE, "got %u packet from %s", (unsigned int)type,
|
|
get_peer_addr());
|
|
switch (type) {
|
|
case P2P_WAKE:
|
|
break;
|
|
case P2P_CHECK:
|
|
psync_p2p_check((packet_check *)packet);
|
|
pdbg_logf(D_NOTICE, "processed P2P packed");
|
|
break;
|
|
default:
|
|
pdbg_logf(D_BUG, "handler for packet type %u not implemented", (unsigned)type);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int socket_write_all(int sock, const void *buff, size_t len) {
|
|
ssize_t ret;
|
|
while (len) {
|
|
ret = write(sock, buff, len);
|
|
if (ret == SOCKET_ERROR) {
|
|
if (errno == EINTR || errno == EAGAIN ||
|
|
errno == EWOULDBLOCK)
|
|
continue;
|
|
return -1;
|
|
}
|
|
buff = (const char *)buff + ret;
|
|
len -= ret;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int socket_read_all(int sock, void *buff, size_t len) {
|
|
ssize_t ret;
|
|
while (len) {
|
|
ret = read(sock, buff, len);
|
|
if (ret == SOCKET_ERROR) {
|
|
if (errno == EINTR || errno == EAGAIN ||
|
|
errno == EWOULDBLOCK)
|
|
continue;
|
|
return -1;
|
|
} else if (ret == 0)
|
|
return -1;
|
|
buff = (char *)buff + ret;
|
|
len -= ret;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int check_token(char *token, uint32_t tlen, unsigned char *key,
|
|
uint32_t keylen, unsigned char *hashhex) {
|
|
binparam params[] = {
|
|
PAPI_LSTR(PSYNC_CHECKSUM, hashhex, PSYNC_HASH_DIGEST_HEXLEN),
|
|
PAPI_LSTR("keydata", key, keylen), PAPI_LSTR("token", token, tlen)};
|
|
psock_t *api;
|
|
binresult *res;
|
|
uint64_t result;
|
|
api = psync_apipool_get();
|
|
if (pdbg_unlikely(!api))
|
|
return 0;
|
|
res = papi_send2(api, "checkfileownershiptoken", params);
|
|
if (pdbg_unlikely(!res)) {
|
|
psync_apipool_release_bad(api);
|
|
return 0;
|
|
}
|
|
psync_apipool_release(api);
|
|
result = papi_find_result2(res, "result", PARAM_NUM)->num;
|
|
pmem_free(PMEM_SUBSYS_OTHER, res);
|
|
return result ? 0 : 1;
|
|
}
|
|
|
|
static void psync_p2p_tcphandler(void *ptr) {
|
|
packet_get packet;
|
|
psync_fileid_t localfileid;
|
|
psync_binary_rsa_key_t binpubrsa;
|
|
psync_rsa_publickey_t pubrsa;
|
|
psync_symmetric_key_t aeskey;
|
|
psync_encrypted_symmetric_key_t encaeskey;
|
|
pcrypto_ctr_encdec_t encoder;
|
|
char *token, *localpath;
|
|
uint64_t off;
|
|
size_t rd;
|
|
int sock;
|
|
int fd;
|
|
uint32_t keylen, enctype;
|
|
unsigned char hashhex[PSYNC_HASH_DIGEST_HEXLEN], buff[4096];
|
|
sock = *((int *)ptr);
|
|
pmem_free(PMEM_SUBSYS_OTHER, ptr);
|
|
pdbg_logf(D_NOTICE, "got tcp connection");
|
|
if (pdbg_unlikely(socket_read_all(sock, &packet, sizeof(packet))))
|
|
goto err0;
|
|
if (pdbg_unlikely(packet.keylen > PSYNC_P2P_RSA_SIZE) ||
|
|
pdbg_unlikely(packet.tokenlen >
|
|
512)) /* lets allow 8 times larger keys than we use */
|
|
goto err0;
|
|
localfileid = has_file(packet.hashstart, packet.genhash,
|
|
packet.rand, packet.filesize, hashhex);
|
|
if (!localfileid) {
|
|
pdbg_logf(D_WARNING, "got request for file that we do not have");
|
|
goto err0;
|
|
}
|
|
binpubrsa = pssl_alloc_binary_rsa(packet.keylen);
|
|
if (pdbg_unlikely(
|
|
socket_read_all(sock, binpubrsa->data, binpubrsa->datalen))) {
|
|
pmem_free(PMEM_SUBSYS_OTHER, binpubrsa);
|
|
goto err0;
|
|
}
|
|
token = pmem_malloc(PMEM_SUBSYS_OTHER, sizeof(char) * packet.tokenlen);
|
|
if (pdbg_unlikely(socket_read_all(sock, token, packet.tokenlen)) ||
|
|
pdbg_unlikely(!check_token(token, packet.tokenlen, binpubrsa->data,
|
|
packet.keylen, hashhex))) {
|
|
pmem_free(PMEM_SUBSYS_OTHER, binpubrsa);
|
|
pmem_free(PMEM_SUBSYS_OTHER, token);
|
|
goto err0;
|
|
}
|
|
pmem_free(PMEM_SUBSYS_OTHER, token);
|
|
pubrsa = prsa_binary_to_public(binpubrsa);
|
|
pmem_free(PMEM_SUBSYS_OTHER, binpubrsa);
|
|
if (pdbg_unlikely(pubrsa == PSYNC_INVALID_RSA))
|
|
goto err0;
|
|
localpath = pfolder_lpath_lfile(localfileid, NULL);
|
|
if (pdbg_unlikely(!localpath))
|
|
goto err0;
|
|
fd = pfile_open(localpath, O_RDONLY, 0);
|
|
pdbg_logf(D_NOTICE, "sending file %s to peer", localpath);
|
|
pmem_free(PMEM_SUBSYS_OTHER, localpath);
|
|
if (fd == INVALID_HANDLE_VALUE) {
|
|
pdbg_logf(D_WARNING, "could not open local file %lu",
|
|
(unsigned long)localfileid);
|
|
goto err0;
|
|
}
|
|
aeskey = pcrypto_key();
|
|
encaeskey = psymkey_encrypt(pubrsa, aeskey);
|
|
encoder = pcrypto_ctr_encdec_create(aeskey);
|
|
psymkey_free(aeskey);
|
|
keylen = encaeskey->datalen;
|
|
enctype = P2P_ENCTYPE_RSA_AES;
|
|
if (pdbg_unlikely(encaeskey == PSYNC_INVALID_ENC_SYM_KEY) ||
|
|
pdbg_unlikely(encoder == PSYNC_CRYPTO_INVALID_ENCODER) ||
|
|
pdbg_unlikely(
|
|
socket_write_all(sock, &keylen, sizeof(keylen)) ||
|
|
socket_write_all(sock, &enctype, sizeof(enctype)) ||
|
|
socket_write_all(sock, encaeskey->data, encaeskey->datalen))) {
|
|
if (encaeskey != PSYNC_INVALID_ENC_SYM_KEY)
|
|
pmem_free(PMEM_SUBSYS_OTHER, encaeskey);
|
|
if (encoder != PSYNC_CRYPTO_INVALID_ENCODER)
|
|
pcrypto_ctr_encdec_free(encoder);
|
|
pfile_close(fd);
|
|
goto err0;
|
|
}
|
|
pmem_free(PMEM_SUBSYS_OTHER, encaeskey);
|
|
off = 0;
|
|
while (off < packet.filesize) {
|
|
if (packet.filesize - off < sizeof(buff))
|
|
rd = packet.filesize - off;
|
|
else
|
|
rd = sizeof(buff);
|
|
if (pdbg_unlikely(pfile_read(fd, buff, rd) != rd))
|
|
break;
|
|
pcrypto_ctr_encdec_decode(encoder, buff, rd, off);
|
|
if (pdbg_unlikely(socket_write_all(sock, buff, rd)))
|
|
break;
|
|
off += rd;
|
|
}
|
|
pcrypto_ctr_encdec_free(encoder);
|
|
pfile_close(fd);
|
|
pdbg_logf(D_NOTICE, "file sent successfuly");
|
|
err0:
|
|
close(sock);
|
|
}
|
|
|
|
static void psync_p2p_thread() {
|
|
ssize_t ret;
|
|
char buff[2048];
|
|
/* struct sockaddr_in6 addr; */
|
|
struct sockaddr_in addr4;
|
|
int tcpsock, socks[2], *inconn;
|
|
socklen_t sl;
|
|
int sret;
|
|
pstatus_wait_statuses_arr(requiredstatuses, ARRAY_SIZE(requiredstatuses));
|
|
tcpsock = INVALID_SOCKET;
|
|
/* udpsock=psock_create(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
|
|
if (pdbg_unlikely(udpsock==INVALID_SOCKET)){*/
|
|
udpsock = psock_create(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
|
if (pdbg_unlikely(udpsock == INVALID_SOCKET))
|
|
goto ex;
|
|
setsockopt(udpsock, SOL_SOCKET, SO_REUSEADDR, (const char *)&on, sizeof(on));
|
|
memset(&addr4, 0, sizeof(addr4));
|
|
addr4.sin_family = AF_INET;
|
|
addr4.sin_port = htons(PSYNC_P2P_PORT);
|
|
addr4.sin_addr.s_addr = INADDR_ANY;
|
|
if (pdbg_unlikely(bind(udpsock, (struct sockaddr *)&addr4, sizeof(addr4)) ==
|
|
SOCKET_ERROR))
|
|
goto ex;
|
|
/* }
|
|
else{
|
|
setsockopt(udpsock, SOL_SOCKET, SO_REUSEADDR, (const char *)&on,
|
|
sizeof(on)); memset(&addr, 0, sizeof(addr)); addr.sin6_family=AF_INET6;
|
|
addr.sin6_port =htons(PSYNC_P2P_PORT);
|
|
addr.sin6_addr =in6addr_any;
|
|
if (pdbg_unlikely(bind(udpsock, (struct sockaddr *)&addr,
|
|
sizeof(addr))==SOCKET_ERROR)) goto ex;
|
|
}
|
|
tcpsock=psock_create(AF_INET6, SOCK_STREAM, IPPROTO_TCP);
|
|
if (pdbg_unlikely(tcpsock==INVALID_SOCKET)){*/
|
|
tcpsock = psock_create(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
|
if (pdbg_unlikely(tcpsock == INVALID_SOCKET))
|
|
goto ex;
|
|
setsockopt(tcpsock, SOL_SOCKET, SO_REUSEADDR, (const char *)&on, sizeof(on));
|
|
memset(&addr4, 0, sizeof(addr4));
|
|
addr4.sin_family = AF_INET;
|
|
addr4.sin_port = htons(0);
|
|
addr4.sin_addr.s_addr = INADDR_ANY;
|
|
if (pdbg_unlikely(bind(tcpsock, (struct sockaddr *)&addr4, sizeof(addr4)) ==
|
|
SOCKET_ERROR))
|
|
goto ex;
|
|
sl = sizeof(addr4);
|
|
if (pdbg_unlikely(getsockname(tcpsock, (struct sockaddr *)&addr4, &sl) ==
|
|
SOCKET_ERROR))
|
|
goto ex;
|
|
tcpport = ntohs(addr4.sin_port);
|
|
/* }
|
|
else{
|
|
setsockopt(tcpsock, SOL_SOCKET, SO_REUSEADDR, (const char *)&on,
|
|
sizeof(on)); memset(&addr, 0, sizeof(addr)); addr.sin6_family=AF_INET6;
|
|
addr.sin6_port =htons(0);
|
|
addr.sin6_addr =in6addr_any;
|
|
if (pdbg_unlikely(bind(tcpsock, (struct sockaddr *)&addr,
|
|
sizeof(addr))==SOCKET_ERROR)) goto ex; sl=sizeof(addr); if
|
|
(pdbg_unlikely(getsockname(tcpsock, (struct sockaddr *)&addr,
|
|
&sl)==SOCKET_ERROR)) goto ex; tcpport=ntohs(addr.sin6_port);
|
|
}*/
|
|
if (pdbg_unlikely(listen(tcpsock, 2)))
|
|
goto ex;
|
|
socks[0] = udpsock;
|
|
socks[1] = tcpsock;
|
|
while (psync_do_run) {
|
|
if (unlikely(!psync_setting_get_bool(_PS(p2psync)))) {
|
|
pthread_mutex_lock(&p2pmutex);
|
|
if (!psync_setting_get_bool(_PS(p2psync))) {
|
|
running = 0;
|
|
close(tcpsock);
|
|
close(udpsock);
|
|
pthread_mutex_unlock(&p2pmutex);
|
|
return;
|
|
}
|
|
pthread_mutex_unlock(&p2pmutex);
|
|
}
|
|
pstatus_wait_statuses_arr(requiredstatuses, ARRAY_SIZE(requiredstatuses));
|
|
sret = psock_select_in(socks, 2, -1);
|
|
if (pdbg_unlikely(sret == -1)) {
|
|
psys_sleep_milliseconds(1);
|
|
continue;
|
|
}
|
|
if (sret == 0) {
|
|
paddrlen = sizeof(paddr);
|
|
ret = recvfrom(udpsock, buff, sizeof(buff), 0, (struct sockaddr *)&paddr,
|
|
&paddrlen);
|
|
if (pdbg_likely(ret != SOCKET_ERROR))
|
|
psync_p2p_process_packet(buff, ret);
|
|
else
|
|
psys_sleep_milliseconds(1);
|
|
} else if (sret == 1) {
|
|
inconn = pmem_malloc(PMEM_SUBSYS_OTHER, sizeof(int));
|
|
*inconn = accept(tcpsock, NULL, NULL);
|
|
if (pdbg_unlikely(*inconn == INVALID_SOCKET))
|
|
pmem_free(PMEM_SUBSYS_OTHER, inconn);
|
|
else
|
|
prun_thread1("p2p tcp", psync_p2p_tcphandler, inconn);
|
|
}
|
|
}
|
|
ex:
|
|
pthread_mutex_lock(&p2pmutex);
|
|
running = 0;
|
|
close(tcpsock);
|
|
close(udpsock);
|
|
pthread_mutex_unlock(&p2pmutex);
|
|
}
|
|
|
|
static void psync_p2p_start() {
|
|
pthread_mutex_lock(&p2pmutex);
|
|
prun_thread("p2p", psync_p2p_thread);
|
|
running = 1;
|
|
pthread_mutex_unlock(&p2pmutex);
|
|
}
|
|
|
|
static void psync_p2p_wake() {
|
|
int sock;
|
|
struct sockaddr_in addr;
|
|
packet_type_t pack;
|
|
sock = psock_create(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
|
if (pdbg_unlikely(sock == INVALID_SOCKET))
|
|
return;
|
|
memset(&addr, 0, sizeof(addr));
|
|
addr.sin_family = AF_INET;
|
|
addr.sin_port = htons(PSYNC_P2P_PORT);
|
|
addr.sin_addr.s_addr = htonl(0x7f000001UL);
|
|
pack = P2P_WAKE;
|
|
if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) != SOCKET_ERROR)
|
|
pdbg_assertw(write(sock, &pack, sizeof(pack)) == sizeof(pack));
|
|
close(sock);
|
|
}
|
|
|
|
void pp2p_init() {
|
|
unsigned char computerbin[PSYNC_HASH_DIGEST_LEN];
|
|
pssl_rand_strong(computerbin, PSYNC_HASH_DIGEST_LEN);
|
|
psync_binhex(computername, computerbin, PSYNC_HASH_DIGEST_LEN);
|
|
ptimer_exception_handler(psync_p2p_wake);
|
|
if (!psync_setting_get_bool(_PS(p2psync)))
|
|
return;
|
|
psync_p2p_start();
|
|
}
|
|
|
|
void pp2p_change() {
|
|
if (psync_setting_get_bool(_PS(p2psync)))
|
|
psync_p2p_start();
|
|
else
|
|
psync_p2p_wake();
|
|
}
|
|
|
|
static int psync_p2p_check_rsa() {
|
|
static pthread_mutex_t rsa_lock = PTHREAD_MUTEX_INITIALIZER;
|
|
pthread_mutex_lock(&rsa_lock);
|
|
if (privkey == PSYNC_INVALID_RSA) {
|
|
psync_rsa_t rsa;
|
|
psync_rsa_privatekey_t rsapriv;
|
|
psync_rsa_publickey_t rsapub;
|
|
psync_binary_rsa_key_t rsapubbin;
|
|
pdbg_logf(D_NOTICE, "generating %ubit RSA key", PSYNC_P2P_RSA_SIZE);
|
|
rsa = pssl_gen_rsa(PSYNC_P2P_RSA_SIZE);
|
|
pdbg_logf(D_NOTICE, "key generated");
|
|
if (pdbg_unlikely(rsa == PSYNC_INVALID_RSA))
|
|
goto rete;
|
|
rsapriv = prsa_get_private(rsa);
|
|
rsapub = prsa_get_public(rsa);
|
|
if (pdbg_likely(rsapub != PSYNC_INVALID_RSA))
|
|
rsapubbin = prsa_public_to_binary(rsapub);
|
|
else
|
|
rsapubbin = PSYNC_INVALID_BIN_RSA;
|
|
pssl_free_rsa(rsa);
|
|
if (pdbg_likely(rsapriv != PSYNC_INVALID_RSA &&
|
|
rsapub != PSYNC_INVALID_RSA &&
|
|
rsapubbin != PSYNC_INVALID_BIN_RSA)) {
|
|
pubkey = rsapriv;
|
|
privkey = rsapub;
|
|
pubkeybin = rsapubbin;
|
|
goto ret0;
|
|
} else {
|
|
if (rsapriv != PSYNC_INVALID_RSA)
|
|
prsa_free_private(rsapriv);
|
|
if (rsapub != PSYNC_INVALID_RSA)
|
|
prsa_free_public(rsapub);
|
|
if (rsapubbin != PSYNC_INVALID_BIN_RSA)
|
|
prsa_free_binary(rsapubbin);
|
|
goto rete;
|
|
}
|
|
}
|
|
ret0:
|
|
pthread_mutex_unlock(&rsa_lock);
|
|
return 0;
|
|
rete:
|
|
pthread_mutex_unlock(&rsa_lock);
|
|
return -1;
|
|
}
|
|
|
|
static int psync_p2p_get_download_token(psync_fileid_t fileid,
|
|
const unsigned char *filehashhex,
|
|
uint64_t fsize, unsigned char **token,
|
|
size_t *tlen) {
|
|
binparam params[] = {
|
|
PAPI_STR("auth", psync_my_auth), PAPI_NUM("fileid", fileid),
|
|
PAPI_NUM("filesize", fsize),
|
|
PAPI_LSTR(PSYNC_CHECKSUM, filehashhex, PSYNC_HASH_DIGEST_HEXLEN),
|
|
PAPI_LSTR("keydata", pubkeybin->data,
|
|
pubkeybin->datalen)};
|
|
psock_t *api;
|
|
binresult *res;
|
|
const binresult *ctoken;
|
|
*token = NULL; /* especially for gcc */
|
|
*tlen = 0;
|
|
api = psync_apipool_get();
|
|
if (pdbg_unlikely(!api))
|
|
return PSYNC_NET_TEMPFAIL;
|
|
res = papi_send2(api, "getfileownershiptoken", params);
|
|
if (pdbg_unlikely(!res)) {
|
|
psync_apipool_release_bad(api);
|
|
return PSYNC_NET_TEMPFAIL;
|
|
}
|
|
psync_apipool_release(api);
|
|
if (pdbg_unlikely(papi_find_result2(res, "result", PARAM_NUM)->num != 0)) {
|
|
pmem_free(PMEM_SUBSYS_OTHER, res);
|
|
return PSYNC_NET_PERMFAIL;
|
|
}
|
|
ctoken = papi_find_result2(res, "token", PARAM_STR);
|
|
*token = pmem_malloc(PMEM_SUBSYS_OTHER, ctoken->length + 1);
|
|
memcpy(*token, ctoken->str, ctoken->length + 1);
|
|
*tlen = ctoken->length;
|
|
pmem_free(PMEM_SUBSYS_OTHER, res);
|
|
return PSYNC_NET_OK;
|
|
}
|
|
|
|
static int psync_p2p_download(int sock, psync_fileid_t fileid,
|
|
const unsigned char *filehashhex, uint64_t fsize,
|
|
const char *filename) {
|
|
uint32_t keylen = 0, enctype = 0;
|
|
psync_symmetric_key_t key;
|
|
psync_encrypted_symmetric_key_t ekey;
|
|
pcrypto_ctr_encdec_t decoder;
|
|
psync_hash_ctx hashctx;
|
|
uint64_t off;
|
|
size_t rd;
|
|
int fd;
|
|
unsigned char buff[4096];
|
|
unsigned char hashbin[PSYNC_HASH_DIGEST_LEN],
|
|
hashhex[PSYNC_HASH_DIGEST_HEXLEN];
|
|
if (pdbg_unlikely(socket_read_all(sock, &keylen, sizeof(keylen)) ||
|
|
socket_read_all(sock, &enctype, sizeof(enctype))))
|
|
return PSYNC_NET_TEMPFAIL;
|
|
if (enctype != P2P_ENCTYPE_RSA_AES) {
|
|
pdbg_logf(D_ERROR, "unknown encryption type %u", (unsigned)enctype);
|
|
return PSYNC_NET_PERMFAIL;
|
|
}
|
|
if (keylen > PSYNC_P2P_RSA_SIZE / 8 *
|
|
2) { /* PSYNC_P2P_RSA_SIZE/8 is enough actually */
|
|
pdbg_logf(D_ERROR, "too long key - %u bytes", (unsigned)keylen);
|
|
return PSYNC_NET_PERMFAIL;
|
|
}
|
|
ekey = psymkey_alloc_encrypted(keylen);
|
|
if (pdbg_unlikely(socket_read_all(sock, ekey->data, keylen)) ||
|
|
pdbg_unlikely((key = prsa_decrypt_symm_key_lock(
|
|
&privkey, &ekey)) == PSYNC_INVALID_SYM_KEY)) {
|
|
// pdbg_unlikely((key=psymkey_decrypt(&psync_rsa_private,
|
|
// &ekey))==PSYNC_INVALID_SYM_KEY)){
|
|
pmem_free(PMEM_SUBSYS_OTHER, ekey);
|
|
return PSYNC_NET_TEMPFAIL;
|
|
}
|
|
pmem_free(PMEM_SUBSYS_OTHER, ekey);
|
|
decoder = pcrypto_ctr_encdec_create(key);
|
|
psymkey_free(key);
|
|
if (decoder == PSYNC_CRYPTO_INVALID_ENCODER)
|
|
return PSYNC_NET_PERMFAIL;
|
|
fd = pfile_open(filename, O_WRONLY, O_CREAT | O_TRUNC);
|
|
if (unlikely(fd == INVALID_HANDLE_VALUE)) {
|
|
pcrypto_ctr_encdec_free(decoder);
|
|
pdbg_logf(D_ERROR, "could not open %s", filename);
|
|
return PSYNC_NET_PERMFAIL;
|
|
}
|
|
off = 0;
|
|
psync_hash_init(&hashctx);
|
|
while (off < fsize) {
|
|
if (fsize - off > sizeof(buff))
|
|
rd = sizeof(buff);
|
|
else
|
|
rd = fsize - off;
|
|
if (pdbg_unlikely(socket_read_all(sock, buff, rd)))
|
|
goto err0;
|
|
pcrypto_ctr_encdec_decode(decoder, buff, rd, off);
|
|
if (pdbg_unlikely(pfile_write(fd, buff, rd) != rd))
|
|
goto err0;
|
|
psync_hash_update(&hashctx, buff, rd);
|
|
off += rd;
|
|
}
|
|
pcrypto_ctr_encdec_free(decoder);
|
|
pfile_close(fd);
|
|
psync_hash_final(hashbin, &hashctx);
|
|
psync_binhex(hashhex, hashbin, PSYNC_HASH_DIGEST_LEN);
|
|
pdbg_logf(D_NOTICE, "downloaded file %s from peer", filename);
|
|
if (memcmp(hashhex, filehashhex, PSYNC_HASH_DIGEST_HEXLEN)) {
|
|
/* it is better to return permanent fail and let the block checksum algo to
|
|
* find bad blocks */
|
|
pdbg_logf(D_WARNING, "got bad checksum for file %s", filename);
|
|
return PSYNC_NET_PERMFAIL;
|
|
} else
|
|
return PSYNC_NET_OK;
|
|
err0:
|
|
pcrypto_ctr_encdec_free(decoder);
|
|
pfile_close(fd);
|
|
psync_hash_final(hashbin, &hashctx);
|
|
return PSYNC_NET_TEMPFAIL;
|
|
}
|
|
|
|
int pp2p_check_download(psync_fileid_t fileid,
|
|
const unsigned char *filehashhex, uint64_t fsize,
|
|
const char *filename) {
|
|
struct sockaddr_in6 addr;
|
|
fd_set rfds;
|
|
packet_check pct1;
|
|
packet_get pct2;
|
|
packet_check_resp resp;
|
|
struct timeval tv;
|
|
psock_ifaces_t *il;
|
|
int *sockets;
|
|
size_t i, tlen;
|
|
int sock, msock;
|
|
packet_resp_t bresp;
|
|
unsigned char hashsource[PSYNC_HASH_BLOCK_SIZE],
|
|
hashbin[PSYNC_HASH_DIGEST_LEN], hashhex[PSYNC_HASH_DIGEST_HEXLEN];
|
|
unsigned char *token;
|
|
socklen_t slen;
|
|
int sret;
|
|
if (!psync_setting_get_bool(_PS(p2psync)))
|
|
return PSYNC_NET_PERMFAIL;
|
|
pdbg_logf(D_NOTICE,
|
|
"sending P2P_CHECK for file with hash %." NTO_STR(
|
|
PSYNC_HASH_DIGEST_HEXLEN) "s",
|
|
filehashhex);
|
|
pct1.type = P2P_CHECK;
|
|
memcpy(pct1.hashstart, filehashhex, PSYNC_P2P_HEXHASH_BYTES);
|
|
pct1.filesize = fsize;
|
|
pssl_rand_strong(pct1.rand, sizeof(pct1.rand));
|
|
memcpy(hashsource, filehashhex, PSYNC_HASH_DIGEST_HEXLEN);
|
|
memcpy(hashsource + PSYNC_HASH_DIGEST_HEXLEN, pct1.rand, sizeof(pct1.rand));
|
|
psync_hash(hashsource, PSYNC_HASH_BLOCK_SIZE, hashbin);
|
|
psync_binhex(pct1.genhash, hashbin, PSYNC_HASH_DIGEST_LEN);
|
|
memcpy(pct1.computername, computername, PSYNC_HASH_DIGEST_HEXLEN);
|
|
il = psock_list_adapters();
|
|
sockets = pmem_malloc(PMEM_SUBSYS_OTHER, sizeof(int) * il->interfacecnt);
|
|
FD_ZERO(&rfds);
|
|
msock = 0;
|
|
for (i = 0; i < il->interfacecnt; i++) {
|
|
sockets[i] = INVALID_SOCKET;
|
|
sock = psock_create(il->interfaces[i].address.ss_family, SOCK_DGRAM,
|
|
IPPROTO_UDP);
|
|
if (unlikely(sock == INVALID_SOCKET)) {
|
|
pdbg_logf(D_NOTICE, "could not create a socket for address family %u",
|
|
(unsigned)il->interfaces[i].address.ss_family);
|
|
continue;
|
|
}
|
|
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char *)&on, sizeof(on));
|
|
setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (const char *)&on, sizeof(on));
|
|
if (pdbg_unlikely(bind(sock, (struct sockaddr *)&il->interfaces[i].address,
|
|
il->interfaces[i].addrsize) == SOCKET_ERROR)) {
|
|
close(sock);
|
|
continue;
|
|
}
|
|
if (il->interfaces[i].broadcast.ss_family == AF_INET)
|
|
((struct sockaddr_in *)(&il->interfaces[i].broadcast))->sin_port =
|
|
htons(PSYNC_P2P_PORT);
|
|
else if (il->interfaces[i].broadcast.ss_family == AF_INET6)
|
|
((struct sockaddr_in6 *)(&il->interfaces[i].broadcast))->sin6_port =
|
|
htons(PSYNC_P2P_PORT);
|
|
if (sendto(sock, (const char *)&pct1, sizeof(pct1), 0,
|
|
(struct sockaddr *)&il->interfaces[i].broadcast,
|
|
il->interfaces[i].addrsize) != SOCKET_ERROR) {
|
|
sockets[i] = sock;
|
|
FD_SET(sock, &rfds);
|
|
if (sock >= msock)
|
|
msock = sock + 1;
|
|
} else
|
|
close(sock);
|
|
}
|
|
if (pdbg_unlikely(!msock))
|
|
goto err_perm;
|
|
tv.tv_sec = PSYNC_P2P_INITIAL_TIMEOUT / 1000;
|
|
tv.tv_usec = (PSYNC_P2P_INITIAL_TIMEOUT % 1000) * 1000;
|
|
sret = select(msock, &rfds, NULL, NULL, &tv);
|
|
if (sret == 0 || pdbg_unlikely(sret == SOCKET_ERROR))
|
|
goto err_perm;
|
|
bresp = P2P_RESP_NOPE;
|
|
for (i = 0; i < il->interfacecnt; i++)
|
|
if (sockets[i] != INVALID_SOCKET && FD_ISSET(sockets[i], &rfds)) {
|
|
slen = sizeof(addr);
|
|
sret = recvfrom(sockets[i], (char *)&resp, sizeof(resp), 0,
|
|
(struct sockaddr *)&addr, &slen);
|
|
if (pdbg_unlikely(sret == SOCKET_ERROR) ||
|
|
pdbg_unlikely(sret < sizeof(resp)))
|
|
continue;
|
|
if (!memcmp(pct1.rand, resp.rand, sizeof(resp.rand))) {
|
|
pdbg_logf(
|
|
D_WARNING,
|
|
"clients are supposed to generate random data, not to reuse mine");
|
|
continue;
|
|
}
|
|
memcpy(hashsource, filehashhex, PSYNC_HASH_DIGEST_HEXLEN);
|
|
memcpy(hashsource + PSYNC_HASH_DIGEST_HEXLEN, resp.rand,
|
|
sizeof(resp.rand));
|
|
psync_hash(hashsource, PSYNC_HASH_BLOCK_SIZE, hashbin);
|
|
psync_binhex(hashhex, hashbin, PSYNC_HASH_DIGEST_LEN);
|
|
if (pdbg_unlikely(memcmp(hashhex, resp.genhash, PSYNC_HASH_DIGEST_HEXLEN)))
|
|
continue;
|
|
if (resp.type == P2P_RESP_HAVEIT) {
|
|
pdbg_logf(D_NOTICE, "got P2P_RESP_HAVEIT");
|
|
bresp = P2P_RESP_HAVEIT;
|
|
break;
|
|
} else if (resp.type == P2P_RESP_WAIT && bresp == P2P_RESP_NOPE)
|
|
bresp = P2P_RESP_WAIT;
|
|
}
|
|
for (i = 0; i < il->interfacecnt; i++)
|
|
if (sockets[i] != INVALID_SOCKET)
|
|
close(sockets[i]);
|
|
pmem_free(PMEM_SUBSYS_OTHER, il);
|
|
pmem_free(PMEM_SUBSYS_OTHER, sockets);
|
|
if (bresp == P2P_RESP_NOPE)
|
|
goto err_perm2;
|
|
else if (bresp == P2P_RESP_WAIT) {
|
|
uint32_t rnd;
|
|
pssl_rand_strong((unsigned char *)&rnd, sizeof(rnd));
|
|
rnd &= 0x7ff;
|
|
psys_sleep_milliseconds(PSYNC_P2P_SLEEP_WAIT_DOWNLOAD + rnd);
|
|
goto err_temp2;
|
|
}
|
|
if (psync_p2p_check_rsa())
|
|
goto err_perm2;
|
|
sret =
|
|
psync_p2p_get_download_token(fileid, filehashhex, fsize, &token, &tlen);
|
|
pdbg_logf(D_NOTICE, "got token");
|
|
if (pdbg_unlikely(sret != PSYNC_NET_OK)) {
|
|
if (sret == PSYNC_NET_TEMPFAIL)
|
|
goto err_temp2;
|
|
else
|
|
goto err_perm2;
|
|
}
|
|
if (addr.sin6_family == AF_INET6) {
|
|
sock = psock_create(AF_INET6, SOCK_STREAM, IPPROTO_TCP);
|
|
addr.sin6_port = htons(resp.port);
|
|
} else if (addr.sin6_family == AF_INET) {
|
|
sock = psock_create(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
|
((struct sockaddr_in *)&addr)->sin_port = htons(resp.port);
|
|
} else {
|
|
pdbg_logf(D_ERROR, "unknown address family %u", (unsigned)addr.sin6_family);
|
|
goto err_perm2;
|
|
}
|
|
if (pdbg_unlikely(sock == INVALID_SOCKET))
|
|
goto err_perm3;
|
|
if (unlikely(connect(sock, (struct sockaddr *)&addr, slen) == SOCKET_ERROR)) {
|
|
pdbg_logf(D_WARNING, "could not connect to %s port %u", get_addr(&addr),
|
|
(unsigned)resp.port);
|
|
goto err_perm3;
|
|
}
|
|
pdbg_logf(D_NOTICE, "connected to peer");
|
|
pct2.type = P2P_GET;
|
|
memcpy(pct2.hashstart, filehashhex, PSYNC_P2P_HEXHASH_BYTES);
|
|
pct2.filesize = fsize;
|
|
pct2.keylen = pubkeybin->datalen;
|
|
pct2.tokenlen = tlen;
|
|
memcpy(pct2.rand, pct1.rand, sizeof(pct1.rand));
|
|
memcpy(pct2.genhash, pct1.genhash, sizeof(pct1.genhash));
|
|
memcpy(pct2.computername, computername, PSYNC_HASH_DIGEST_HEXLEN);
|
|
if (socket_write_all(sock, &pct2, sizeof(pct2)) ||
|
|
socket_write_all(sock, pubkeybin->data,
|
|
pubkeybin->datalen) ||
|
|
socket_write_all(sock, token, tlen)) {
|
|
pdbg_logf(D_WARNING, "writing to socket failed");
|
|
goto err_temp3;
|
|
}
|
|
pmem_free(PMEM_SUBSYS_OTHER, token);
|
|
sret = psync_p2p_download(sock, fileid, filehashhex, fsize, filename);
|
|
close(sock);
|
|
return sret;
|
|
err_perm3:
|
|
pmem_free(PMEM_SUBSYS_OTHER, token);
|
|
goto err_perm2;
|
|
err_perm:
|
|
for (i = 0; i < il->interfacecnt; i++)
|
|
if (sockets[i] != INVALID_SOCKET)
|
|
close(sockets[i]);
|
|
pmem_free(PMEM_SUBSYS_OTHER, il);
|
|
pmem_free(PMEM_SUBSYS_OTHER, sockets);
|
|
err_perm2:
|
|
return PSYNC_NET_PERMFAIL;
|
|
err_temp3:
|
|
close(sock);
|
|
pmem_free(PMEM_SUBSYS_OTHER, token);
|
|
err_temp2:
|
|
return PSYNC_NET_TEMPFAIL;
|
|
}
|