847 lines
30 KiB
C
847 lines
30 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 "pcompat.h"
|
|
#include "psettings.h"
|
|
#include "plibs.h"
|
|
#include "ptimer.h"
|
|
#include "pstatus.h"
|
|
#include "pssl.h"
|
|
#include "pdownload.h"
|
|
#include "pnetlibs.h"
|
|
#include "papi.h"
|
|
#include "pp2p.h"
|
|
#include "pcrypto.h"
|
|
#include "pfolder.h"
|
|
#include <string.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 psync_socket_t 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 psync_rsa_public=PSYNC_INVALID_RSA;
|
|
static psync_rsa_privatekey_t psync_rsa_private=PSYNC_INVALID_RSA;
|
|
static psync_binary_rsa_key_t psync_rsa_public_bin=PSYNC_INVALID_BIN_RSA;
|
|
|
|
PSYNC_PURE static const char *p2p_get_address(void *addr){
|
|
if (((struct sockaddr_in *)addr)->sin_family==AF_INET)
|
|
return inet_ntoa(((struct sockaddr_in *)addr)->sin_addr);
|
|
else{
|
|
#if defined(P_OS_POSIX)
|
|
static char buff[80];
|
|
return inet_ntop(AF_INET6, &((struct sockaddr_in6 *)addr)->sin6_addr, buff, sizeof(buff));
|
|
#else
|
|
return "IPv6 address"; /* inet_ntop on Windows is Vista+ */
|
|
#endif
|
|
}
|
|
}
|
|
|
|
PSYNC_PURE static const char *p2p_get_peer_address(){
|
|
if (paddr.ss_family==AF_INET)
|
|
return inet_ntoa(((struct sockaddr_in *)&paddr)->sin_addr);
|
|
else{
|
|
#if defined(P_OS_POSIX)
|
|
static char buff[80];
|
|
return inet_ntop(AF_INET6, &((struct sockaddr_in6 *)&paddr)->sin6_addr, buff, sizeof(buff));
|
|
#else
|
|
return "IPv6 address"; /* inet_ntop on Windows is Vista+ */
|
|
#endif
|
|
}
|
|
}
|
|
|
|
static psync_fileid_t psync_p2p_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=psync_sql_query_rdlock("SELECT id, checksum FROM localfile WHERE checksum LIKE ? AND size=?");
|
|
psync_sql_bind_lstring(res, 1, like, PSYNC_P2P_HEXHASH_BYTES+1);
|
|
psync_sql_bind_uint(res, 2, filesize);
|
|
while ((row=psync_sql_fetch_row(res))){
|
|
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]);
|
|
psync_sql_free_result(res);
|
|
return ret;
|
|
}
|
|
}
|
|
psync_sql_free_result(res);
|
|
return 0;
|
|
}
|
|
|
|
static int psync_p2p_is_downloading(const unsigned char *hashstart, const unsigned char *genhash, const unsigned char *rand, uint64_t filesize,
|
|
unsigned char *realhash){
|
|
downloading_files_hashes *hashes;
|
|
unsigned char hashsource[PSYNC_HASH_BLOCK_SIZE], hashbin[PSYNC_HASH_DIGEST_LEN], hashhex[PSYNC_HASH_DIGEST_HEXLEN];
|
|
size_t i;
|
|
hashes=psync_get_downloading_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);
|
|
psync_free(hashes);
|
|
return 1;
|
|
}
|
|
}
|
|
psync_free(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 (psync_p2p_has_file(packet->hashstart, packet->genhash, packet->rand, packet->filesize, hashhex))
|
|
resp.type=P2P_RESP_HAVEIT;
|
|
else if (psync_p2p_is_downloading(packet->hashstart, packet->genhash, packet->rand, packet->filesize, hashhex))
|
|
resp.type=P2P_RESP_WAIT;
|
|
else
|
|
return;
|
|
resp.port=tcpport;
|
|
psync_ssl_rand_weak(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);
|
|
debug(D_NOTICE, "replying with %u to a check from %s, looking for %."NTO_STR(PSYNC_HASH_DIGEST_HEXLEN)"s", (unsigned int)resp.type, p2p_get_peer_address(), hashhex);
|
|
if (files_serving)
|
|
psync_milisleep(files_serving*10);
|
|
if (resp.type==P2P_RESP_WAIT)
|
|
psync_milisleep(PSYNC_P2P_INITIAL_TIMEOUT/4);
|
|
if (!sendto(udpsock, (const char *)&resp, sizeof(resp), 0, (const struct sockaddr *)&paddr, paddrlen))
|
|
debug(D_WARNING, "sendto to %s failed", p2p_get_peer_address());
|
|
}
|
|
|
|
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;
|
|
debug(D_NOTICE, "got %u packet from %s", (unsigned int)type, p2p_get_peer_address());
|
|
switch (type){
|
|
case P2P_WAKE:
|
|
break;
|
|
case P2P_CHECK:
|
|
psync_p2p_check((packet_check *)packet);
|
|
debug(D_NOTICE, "processed P2P packed");
|
|
break;
|
|
default:
|
|
debug(D_BUG, "handler for packet type %u not implemented", (unsigned)type);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int socket_write_all(psync_socket_t sock, const void *buff, size_t len){
|
|
ssize_t ret;
|
|
while (len){
|
|
ret=psync_write_socket(sock, buff, len);
|
|
if (ret==SOCKET_ERROR){
|
|
if (psync_sock_err()==P_INTR || psync_sock_err()==P_AGAIN || psync_sock_err()==P_WOULDBLOCK)
|
|
continue;
|
|
return -1;
|
|
}
|
|
buff=(const char *)buff+ret;
|
|
len-=ret;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int socket_read_all(psync_socket_t sock, void *buff, size_t len){
|
|
ssize_t ret;
|
|
while (len){
|
|
ret=psync_read_socket(sock, buff, len);
|
|
if (ret==SOCKET_ERROR){
|
|
if (psync_sock_err()==P_INTR || psync_sock_err()==P_AGAIN || psync_sock_err()==P_WOULDBLOCK)
|
|
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[]={P_LSTR(PSYNC_CHECKSUM, hashhex, PSYNC_HASH_DIGEST_HEXLEN),
|
|
P_LSTR("keydata", key, keylen), P_LSTR("token", token, tlen)};
|
|
psync_socket *api;
|
|
binresult *res;
|
|
uint64_t result;
|
|
api=psync_apipool_get();
|
|
if (unlikely_log(!api))
|
|
return 0;
|
|
res=send_command(api, "checkfileownershiptoken", params);
|
|
if (unlikely_log(!res)){
|
|
psync_apipool_release_bad(api);
|
|
return 0;
|
|
}
|
|
psync_apipool_release(api);
|
|
result=psync_find_result(res, "result", PARAM_NUM)->num;
|
|
psync_free(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;
|
|
psync_crypto_aes256_ctr_encoder_decoder_t encoder;
|
|
char *token, *localpath;
|
|
uint64_t off;
|
|
size_t rd;
|
|
psync_socket_t sock;
|
|
psync_file_t fd;
|
|
uint32_t keylen, enctype;
|
|
unsigned char hashhex[PSYNC_HASH_DIGEST_HEXLEN], buff[4096];
|
|
sock=*((psync_socket_t *)ptr);
|
|
psync_free(ptr);
|
|
debug(D_NOTICE, "got tcp connection");
|
|
if (unlikely_log(socket_read_all(sock, &packet, sizeof(packet))))
|
|
goto err0;
|
|
if (unlikely_log(packet.keylen>PSYNC_P2P_RSA_SIZE) || unlikely_log(packet.tokenlen>512)) /* lets allow 8 times larger keys than we use */
|
|
goto err0;
|
|
localfileid=psync_p2p_has_file(packet.hashstart, packet.genhash, packet.rand, packet.filesize, hashhex);
|
|
if (!localfileid){
|
|
debug(D_WARNING, "got request for file that we do not have");
|
|
goto err0;
|
|
}
|
|
binpubrsa=psync_ssl_alloc_binary_rsa(packet.keylen);
|
|
if (unlikely_log(socket_read_all(sock, binpubrsa->data, binpubrsa->datalen))){
|
|
psync_free(binpubrsa);
|
|
goto err0;
|
|
}
|
|
token=psync_new_cnt(char, packet.tokenlen);
|
|
if (unlikely_log(socket_read_all(sock, token, packet.tokenlen)) ||
|
|
unlikely_log(!check_token(token, packet.tokenlen, binpubrsa->data, packet.keylen, hashhex))){
|
|
psync_free(binpubrsa);
|
|
psync_free(token);
|
|
goto err0;
|
|
}
|
|
psync_free(token);
|
|
pubrsa=psync_ssl_rsa_binary_to_public(binpubrsa);
|
|
psync_free(binpubrsa);
|
|
if (unlikely_log(pubrsa==PSYNC_INVALID_RSA))
|
|
goto err0;
|
|
localpath=psync_local_path_for_local_file(localfileid, NULL);
|
|
if (unlikely_log(!localpath))
|
|
goto err0;
|
|
fd=psync_file_open(localpath, P_O_RDONLY, 0);
|
|
debug(D_NOTICE, "sending file %s to peer", localpath);
|
|
psync_free(localpath);
|
|
if (fd==INVALID_HANDLE_VALUE){
|
|
debug(D_WARNING, "could not open local file %lu", (unsigned long)localfileid);
|
|
goto err0;
|
|
}
|
|
aeskey=psync_crypto_aes256_ctr_gen_key();
|
|
encaeskey=psync_ssl_rsa_encrypt_symmetric_key(pubrsa, aeskey);
|
|
encoder=psync_crypto_aes256_ctr_encoder_decoder_create(aeskey);
|
|
psync_ssl_free_symmetric_key(aeskey);
|
|
keylen=encaeskey->datalen;
|
|
enctype=P2P_ENCTYPE_RSA_AES;
|
|
if (unlikely_log(encaeskey==PSYNC_INVALID_ENC_SYM_KEY) || unlikely_log(encoder==PSYNC_CRYPTO_INVALID_ENCODER) ||
|
|
unlikely_log(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)
|
|
psync_free(encaeskey);
|
|
if (encoder!=PSYNC_CRYPTO_INVALID_ENCODER)
|
|
psync_crypto_aes256_ctr_encoder_decoder_free(encoder);
|
|
psync_file_close(fd);
|
|
goto err0;
|
|
}
|
|
psync_free(encaeskey);
|
|
off=0;
|
|
while (off<packet.filesize){
|
|
if (packet.filesize-off<sizeof(buff))
|
|
rd=packet.filesize-off;
|
|
else
|
|
rd=sizeof(buff);
|
|
if (unlikely_log(psync_file_read(fd, buff, rd)!=rd))
|
|
break;
|
|
psync_crypto_aes256_ctr_encode_decode_inplace(encoder, buff, rd, off);
|
|
if (unlikely_log(socket_write_all(sock, buff, rd)))
|
|
break;
|
|
off+=rd;
|
|
}
|
|
psync_crypto_aes256_ctr_encoder_decoder_free(encoder);
|
|
psync_file_close(fd);
|
|
debug(D_NOTICE, "file sent successfuly");
|
|
err0:
|
|
psync_close_socket(sock);
|
|
}
|
|
|
|
static void psync_p2p_thread(){
|
|
ssize_t ret;
|
|
char buff[2048];
|
|
/* struct sockaddr_in6 addr; */
|
|
struct sockaddr_in addr4;
|
|
psync_socket_t tcpsock, socks[2], *inconn;
|
|
socklen_t sl;
|
|
int sret;
|
|
psync_wait_statuses_array(requiredstatuses, ARRAY_SIZE(requiredstatuses));
|
|
tcpsock=INVALID_SOCKET;
|
|
/* udpsock=psync_create_socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
|
|
if (unlikely_log(udpsock==INVALID_SOCKET)){*/
|
|
udpsock=psync_create_socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
|
if (unlikely_log(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 (unlikely_log(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 (unlikely_log(bind(udpsock, (struct sockaddr *)&addr, sizeof(addr))==SOCKET_ERROR))
|
|
goto ex;
|
|
}
|
|
tcpsock=psync_create_socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);
|
|
if (unlikely_log(tcpsock==INVALID_SOCKET)){*/
|
|
tcpsock=psync_create_socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
|
if (unlikely_log(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 (unlikely_log(bind(tcpsock, (struct sockaddr *)&addr4, sizeof(addr4))==SOCKET_ERROR))
|
|
goto ex;
|
|
sl=sizeof(addr4);
|
|
if (unlikely_log(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 (unlikely_log(bind(tcpsock, (struct sockaddr *)&addr, sizeof(addr))==SOCKET_ERROR))
|
|
goto ex;
|
|
sl=sizeof(addr);
|
|
if (unlikely_log(getsockname(tcpsock, (struct sockaddr *)&addr, &sl)==SOCKET_ERROR))
|
|
goto ex;
|
|
tcpport=ntohs(addr.sin6_port);
|
|
}*/
|
|
if (unlikely_log(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;
|
|
psync_close_socket(tcpsock);
|
|
psync_close_socket(udpsock);
|
|
pthread_mutex_unlock(&p2pmutex);
|
|
return;
|
|
}
|
|
pthread_mutex_unlock(&p2pmutex);
|
|
}
|
|
psync_wait_statuses_array(requiredstatuses, ARRAY_SIZE(requiredstatuses));
|
|
sret=psync_select_in(socks, 2, -1);
|
|
if (unlikely_log(sret==-1)){
|
|
psync_milisleep(1);
|
|
continue;
|
|
}
|
|
if (sret==0){
|
|
paddrlen=sizeof(paddr);
|
|
ret=recvfrom(udpsock, buff, sizeof(buff), 0, (struct sockaddr *)&paddr, &paddrlen);
|
|
if (likely_log(ret!=SOCKET_ERROR))
|
|
psync_p2p_process_packet(buff, ret);
|
|
else
|
|
psync_milisleep(1);
|
|
}
|
|
else if (sret==1){
|
|
inconn=psync_new(psync_socket_t);
|
|
*inconn=accept(tcpsock, NULL, NULL);
|
|
if (unlikely_log(*inconn==INVALID_SOCKET))
|
|
psync_free(inconn);
|
|
else
|
|
psync_run_thread1("p2p tcp", psync_p2p_tcphandler, inconn);
|
|
}
|
|
}
|
|
ex:
|
|
pthread_mutex_lock(&p2pmutex);
|
|
running=0;
|
|
psync_close_socket(tcpsock);
|
|
psync_close_socket(udpsock);
|
|
pthread_mutex_unlock(&p2pmutex);
|
|
}
|
|
|
|
static void psync_p2p_start(){
|
|
pthread_mutex_lock(&p2pmutex);
|
|
psync_run_thread("p2p", psync_p2p_thread);
|
|
running=1;
|
|
pthread_mutex_unlock(&p2pmutex);
|
|
}
|
|
|
|
static void psync_p2p_wake(){
|
|
psync_socket_t sock;
|
|
struct sockaddr_in addr;
|
|
packet_type_t pack;
|
|
sock=psync_create_socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
|
if (unlikely_log(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)
|
|
assertw(psync_write_socket(sock, &pack, sizeof(pack))==sizeof(pack));
|
|
psync_close_socket(sock);
|
|
}
|
|
|
|
void psync_p2p_init(){
|
|
unsigned char computerbin[PSYNC_HASH_DIGEST_LEN];
|
|
psync_ssl_rand_weak(computerbin, PSYNC_HASH_DIGEST_LEN);
|
|
psync_binhex(computername, computerbin, PSYNC_HASH_DIGEST_LEN);
|
|
psync_timer_exception_handler(psync_p2p_wake);
|
|
if (!psync_setting_get_bool(_PS(p2psync)))
|
|
return;
|
|
psync_p2p_start();
|
|
}
|
|
|
|
void psync_p2p_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 (psync_rsa_private==PSYNC_INVALID_RSA){
|
|
psync_rsa_t rsa;
|
|
psync_rsa_privatekey_t rsapriv;
|
|
psync_rsa_publickey_t rsapub;
|
|
psync_binary_rsa_key_t rsapubbin;
|
|
debug(D_NOTICE, "generating %ubit RSA key", PSYNC_P2P_RSA_SIZE);
|
|
rsa=psync_ssl_gen_rsa(PSYNC_P2P_RSA_SIZE);
|
|
debug(D_NOTICE, "key generated");
|
|
if (unlikely_log(rsa==PSYNC_INVALID_RSA))
|
|
goto rete;
|
|
rsapriv=psync_ssl_rsa_get_private(rsa);
|
|
rsapub=psync_ssl_rsa_get_public(rsa);
|
|
if (likely_log(rsapub!=PSYNC_INVALID_RSA))
|
|
rsapubbin=psync_ssl_rsa_public_to_binary(rsapub);
|
|
else
|
|
rsapubbin=PSYNC_INVALID_BIN_RSA;
|
|
psync_ssl_free_rsa(rsa);
|
|
if (likely_log(rsapriv!=PSYNC_INVALID_RSA && rsapub!=PSYNC_INVALID_RSA && rsapubbin!=PSYNC_INVALID_BIN_RSA)){
|
|
psync_rsa_private=rsapriv;
|
|
psync_rsa_public=rsapub;
|
|
psync_rsa_public_bin=rsapubbin;
|
|
goto ret0;
|
|
}
|
|
else{
|
|
if (rsapriv!=PSYNC_INVALID_RSA)
|
|
psync_ssl_rsa_free_private(rsapriv);
|
|
if (rsapub!=PSYNC_INVALID_RSA)
|
|
psync_ssl_rsa_free_public(rsapub);
|
|
if (rsapubbin!=PSYNC_INVALID_BIN_RSA)
|
|
psync_ssl_rsa_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[]={P_STR("auth", psync_my_auth), P_NUM("fileid", fileid), P_NUM("filesize", fsize),
|
|
P_LSTR(PSYNC_CHECKSUM, filehashhex, PSYNC_HASH_DIGEST_HEXLEN),
|
|
P_LSTR("keydata", psync_rsa_public_bin->data, psync_rsa_public_bin->datalen)};
|
|
psync_socket *api;
|
|
binresult *res;
|
|
const binresult *ctoken;
|
|
*token=NULL; /* especially for gcc */
|
|
*tlen=0;
|
|
api=psync_apipool_get();
|
|
if (unlikely_log(!api))
|
|
return PSYNC_NET_TEMPFAIL;
|
|
res=send_command(api, "getfileownershiptoken", params);
|
|
if (unlikely_log(!res)){
|
|
psync_apipool_release_bad(api);
|
|
return PSYNC_NET_TEMPFAIL;
|
|
}
|
|
psync_apipool_release(api);
|
|
if (unlikely_log(psync_find_result(res, "result", PARAM_NUM)->num!=0)){
|
|
psync_free(res);
|
|
return PSYNC_NET_PERMFAIL;
|
|
}
|
|
ctoken=psync_find_result(res, "token", PARAM_STR);
|
|
*token=psync_malloc(ctoken->length+1);
|
|
memcpy(*token, ctoken->str, ctoken->length+1);
|
|
*tlen=ctoken->length;
|
|
psync_free(res);
|
|
return PSYNC_NET_OK;
|
|
}
|
|
|
|
static int psync_p2p_download(psync_socket_t sock, psync_fileid_t fileid, const unsigned char *filehashhex, uint64_t fsize, const char *filename){
|
|
uint32_t keylen, enctype;
|
|
psync_symmetric_key_t key;
|
|
psync_encrypted_symmetric_key_t ekey;
|
|
psync_crypto_aes256_ctr_encoder_decoder_t decoder;
|
|
psync_hash_ctx hashctx;
|
|
uint64_t off;
|
|
size_t rd;
|
|
psync_file_t fd;
|
|
unsigned char buff[4096];
|
|
unsigned char hashbin[PSYNC_HASH_DIGEST_LEN], hashhex[PSYNC_HASH_DIGEST_HEXLEN];
|
|
if (unlikely_log(socket_read_all(sock, &keylen, sizeof(keylen)) || socket_read_all(sock, &enctype, sizeof(enctype))))
|
|
return PSYNC_NET_TEMPFAIL;
|
|
if (enctype!=P2P_ENCTYPE_RSA_AES){
|
|
debug(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 */
|
|
debug(D_ERROR, "too long key - %u bytes", (unsigned)keylen);
|
|
return PSYNC_NET_PERMFAIL;
|
|
}
|
|
ekey=psync_ssl_alloc_encrypted_symmetric_key(keylen);
|
|
if (unlikely_log(socket_read_all(sock, ekey->data, keylen)) ||
|
|
unlikely_log((key = psync_ssl_rsa_decrypt_symm_key_lock(&psync_rsa_private, &ekey)) == PSYNC_INVALID_SYM_KEY)) {
|
|
//unlikely_log((key=psync_ssl_rsa_decrypt_symmetric_key(&psync_rsa_private, &ekey))==PSYNC_INVALID_SYM_KEY)){
|
|
psync_free(ekey);
|
|
return PSYNC_NET_TEMPFAIL;
|
|
}
|
|
psync_free(ekey);
|
|
decoder=psync_crypto_aes256_ctr_encoder_decoder_create(key);
|
|
psync_ssl_free_symmetric_key(key);
|
|
if (decoder==PSYNC_CRYPTO_INVALID_ENCODER)
|
|
return PSYNC_NET_PERMFAIL;
|
|
fd=psync_file_open(filename, P_O_WRONLY, P_O_CREAT|P_O_TRUNC);
|
|
if (unlikely(fd==INVALID_HANDLE_VALUE)){
|
|
psync_crypto_aes256_ctr_encoder_decoder_free(decoder);
|
|
debug(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 (unlikely_log(socket_read_all(sock, buff, rd)))
|
|
goto err0;
|
|
psync_crypto_aes256_ctr_encode_decode_inplace(decoder, buff, rd, off);
|
|
if (unlikely_log(psync_file_write(fd, buff, rd)!=rd))
|
|
goto err0;
|
|
psync_hash_update(&hashctx, buff, rd);
|
|
off+=rd;
|
|
}
|
|
psync_crypto_aes256_ctr_encoder_decoder_free(decoder);
|
|
psync_file_close(fd);
|
|
psync_hash_final(hashbin, &hashctx);
|
|
psync_binhex(hashhex, hashbin, PSYNC_HASH_DIGEST_LEN);
|
|
debug(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 */
|
|
debug(D_WARNING, "got bad checksum for file %s", filename);
|
|
return PSYNC_NET_PERMFAIL;
|
|
}
|
|
else
|
|
return PSYNC_NET_OK;
|
|
err0:
|
|
psync_crypto_aes256_ctr_encoder_decoder_free(decoder);
|
|
psync_file_close(fd);
|
|
psync_hash_final(hashbin, &hashctx);
|
|
return PSYNC_NET_TEMPFAIL;
|
|
}
|
|
|
|
int psync_p2p_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;
|
|
psync_interface_list_t *il;
|
|
psync_socket_t *sockets;
|
|
size_t i, tlen;
|
|
psync_socket_t 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;
|
|
debug(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;
|
|
psync_ssl_rand_weak(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=psync_list_ip_adapters();
|
|
sockets=psync_new_cnt(psync_socket_t, il->interfacecnt);
|
|
FD_ZERO(&rfds);
|
|
msock=0;
|
|
for (i=0; i<il->interfacecnt; i++){
|
|
sockets[i]=INVALID_SOCKET;
|
|
sock=psync_create_socket(il->interfaces[i].address.ss_family, SOCK_DGRAM, IPPROTO_UDP);
|
|
if (unlikely(sock==INVALID_SOCKET)){
|
|
debug(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 (unlikely_log(bind(sock, (struct sockaddr *)&il->interfaces[i].address, il->interfaces[i].addrsize)==SOCKET_ERROR)){
|
|
psync_close_socket(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
|
|
psync_close_socket(sock);
|
|
}
|
|
if (unlikely_log(!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 || unlikely_log(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 (unlikely_log(sret==SOCKET_ERROR) || unlikely_log(sret<sizeof(resp)))
|
|
continue;
|
|
if (!memcmp(pct1.rand, resp.rand, sizeof(resp.rand))){
|
|
debug(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 (unlikely_log(memcmp(hashhex, resp.genhash, PSYNC_HASH_DIGEST_HEXLEN)))
|
|
continue;
|
|
if (resp.type==P2P_RESP_HAVEIT){
|
|
debug(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)
|
|
psync_close_socket(sockets[i]);
|
|
psync_free(il);
|
|
psync_free(sockets);
|
|
if (bresp==P2P_RESP_NOPE)
|
|
goto err_perm2;
|
|
else if (bresp==P2P_RESP_WAIT){
|
|
uint32_t rnd;
|
|
psync_ssl_rand_weak((unsigned char *)&rnd, sizeof(rnd));
|
|
rnd&=0x7ff;
|
|
psync_milisleep(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);
|
|
debug(D_NOTICE, "got token");
|
|
if (unlikely_log(sret!=PSYNC_NET_OK)){
|
|
if (sret==PSYNC_NET_TEMPFAIL)
|
|
goto err_temp2;
|
|
else
|
|
goto err_perm2;
|
|
}
|
|
if (addr.sin6_family==AF_INET6){
|
|
sock=psync_create_socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);
|
|
addr.sin6_port=htons(resp.port);
|
|
}
|
|
else if (addr.sin6_family==AF_INET){
|
|
sock=psync_create_socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
|
((struct sockaddr_in *)&addr)->sin_port=htons(resp.port);
|
|
}
|
|
else{
|
|
debug(D_ERROR, "unknown address family %u", (unsigned)addr.sin6_family);
|
|
goto err_perm2;
|
|
}
|
|
if (unlikely_log(sock==INVALID_SOCKET))
|
|
goto err_perm3;
|
|
if (unlikely(connect(sock, (struct sockaddr *)&addr, slen)==SOCKET_ERROR)){
|
|
debug(D_WARNING, "could not connect to %s port %u", p2p_get_address(&addr), (unsigned)resp.port);
|
|
goto err_perm3;
|
|
}
|
|
debug(D_NOTICE, "connected to peer");
|
|
pct2.type=P2P_GET;
|
|
memcpy(pct2.hashstart, filehashhex, PSYNC_P2P_HEXHASH_BYTES);
|
|
pct2.filesize=fsize;
|
|
pct2.keylen=psync_rsa_public_bin->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, psync_rsa_public_bin->data, psync_rsa_public_bin->datalen) ||
|
|
socket_write_all(sock, token, tlen)){
|
|
debug(D_WARNING, "writing to socket failed");
|
|
goto err_temp3;
|
|
}
|
|
psync_free(token);
|
|
sret=psync_p2p_download(sock, fileid, filehashhex, fsize, filename);
|
|
psync_close_socket(sock);
|
|
return sret;
|
|
err_perm3:
|
|
psync_free(token);
|
|
goto err_perm2;
|
|
err_perm:
|
|
for (i=0; i<il->interfacecnt; i++)
|
|
if (sockets[i]!=INVALID_SOCKET)
|
|
psync_close_socket(sockets[i]);
|
|
psync_free(il);
|
|
psync_free(sockets);
|
|
err_perm2:
|
|
return PSYNC_NET_PERMFAIL;
|
|
err_temp3:
|
|
psync_close_socket(sock);
|
|
psync_free(token);
|
|
err_temp2:
|
|
return PSYNC_NET_TEMPFAIL;
|
|
}
|