/*
(c) Copyright 2012 - 2018 Anton Sviridenko
https://picapica.im
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, version 3.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
#include "PICA_security.h"
#include "PICA_client.h"
#include "PICA_proto.h"
#include "PICA_msgproc.h"
#include "PICA_common.h"
#include "PICA_signverify.h"
#include
#include
#include
#define PICA_DEBUG
#ifdef PICA_DEBUG
#define PICA_TRACEFUNC fprintf(stderr, "%s()\n", __PRETTY_FUNCTION__);
#else
#define PICA_TRACEFUNC
#endif
static struct PICA_client_callbacks callbacks;
#ifdef PICA_MULTITHREADED
#ifdef WIN32
static HANDLE *mt_locks;
const char *PICA_inet_ntop(int af, const void *src, char *dst, size_t size);
/* own implementation of inet_ntop() for WinXP compatibility */
#define inet_ntop PICA_inet_ntop
#else
static pthread_mutex_t *mt_locks;
#endif
#endif
static int PICA_send_file_fragment(struct PICA_c2c *chn);
static int PICA_sendfile_open_read(struct PICA_c2c *chn, const char *filename_utf8, uint64_t *file_size);
static int PICA_recvfile_open_write(struct PICA_c2c *chn, const char *filename_utf8, unsigned int filenamesize);
static int PICA_send_filecontrol(struct PICA_c2c *chan, int senderctl, int receiverctl);
static int PICA_send_directc2caddrlist(struct PICA_c2c *chn);
static int PICA_send_directc2cfailed(struct PICA_c2c *chn);
static int PICA_send_directc2c_switch(struct PICA_c2c *chn);
static unsigned int procmsg_INITRESP(unsigned char*, unsigned int, void*);
static unsigned int procmsg_CONNREQINC(unsigned char*, unsigned int, void*);
static unsigned int procmsg_NOTFOUND(unsigned char*, unsigned int, void*);
static unsigned int procmsg_FOUND(unsigned char*, unsigned int, void*);
static unsigned int procmsg_CLNODELIST(unsigned char*, unsigned int, void*);
static unsigned int procmsg_MSGUTF8(unsigned char*, unsigned int, void*);
static unsigned int procmsg_MSGOK(unsigned char*, unsigned int, void*);
static unsigned int procmsg_PINGREQ(unsigned char*, unsigned int, void*);
static unsigned int procmsg_SENDFILEREQUEST(unsigned char*, unsigned int, void*);
static unsigned int procmsg_ACCEPTEDFILE(unsigned char*, unsigned int, void*);
static unsigned int procmsg_DENIEDFILE(unsigned char*, unsigned int, void*);
static unsigned int procmsg_FILEFRAGMENT(unsigned char*, unsigned int, void*);
static unsigned int procmsg_FILECONTROL(unsigned char*, unsigned int, void*);
static unsigned int procmsg_INITRESP_c2c(unsigned char*, unsigned int, void*);
static unsigned int procmsg_C2CCONNREQ(unsigned char*, unsigned int, void*);
static unsigned int procmsg_PICA_PROTO_DIRECTC2C_ADDRLIST(unsigned char*, unsigned int, void*);
static unsigned int procmsg_PICA_PROTO_DIRECTC2C_FAILED(unsigned char*, unsigned int, void*);
static unsigned int procmsg_PICA_PROTO_DIRECTC2C_SWITCH(unsigned char*, unsigned int, void*);
static unsigned int procmsg_MULTILOGIN(unsigned char*, unsigned int, void*);
static unsigned int procmsg_CALLREQ(unsigned char*, unsigned int, void*);
static unsigned int procmsg_CALLANS(unsigned char*, unsigned int, void*);
static unsigned int procmsg_CALLREJ(unsigned char*, unsigned int, void*);
static unsigned int procmsg_CALLFIN(unsigned char*, unsigned int, void*);
static unsigned int procmsg_CALL_AUDIOPARAM(unsigned char*, unsigned int, void*);
static unsigned int procmsg_CALL_VIDEOPARAM(unsigned char*, unsigned int, void*);
static unsigned int procmsg_CALL_AUDIO_PACKET(unsigned char*, unsigned int, void*);
static unsigned int procmsg_CALL_VIDEO_PACKET(unsigned char*, unsigned int, void*);
static struct PICA_proto_msg* c2n_writebuf_push(struct PICA_c2n *ci, unsigned int msgid, unsigned int size);
static struct PICA_proto_msg* c2c_writebuf_push(struct PICA_c2c *chn, unsigned int msgid, unsigned int size);
static int process_first_async_connect_result(int connect_ret);
static int check_async_connect_result(int sock, struct sockaddr* a, int addrlen);
static int process_async_ssl_errors(SSL *ssl, int ret, int *want_write, int *want_read);
static int PICA_write_c2n(struct PICA_c2n *ci);
static int PICA_write_c2c(struct PICA_c2c *chn);
static int PICA_write_ssl(SSL *ssl, unsigned char *buf, unsigned int *ppos, unsigned int *btw);
static int PICA_read_c2n(struct PICA_c2n *ci);
static int PICA_read_c2c(struct PICA_c2c *chn);
static int PICA_read_ssl(SSL *ssl, unsigned char *buf, unsigned int *ppos, unsigned int size);
#define MSGINFO_MSGSNUM(arr) (sizeof(arr)/sizeof(struct PICA_msginfo))
const struct PICA_msginfo c2n_messages[] =
{
{PICA_PROTO_CONNREQINC, PICA_MSG_FIXED_SIZE, PICA_PROTO_CONNREQINC_SIZE, procmsg_CONNREQINC},
{PICA_PROTO_NOTFOUND, PICA_MSG_FIXED_SIZE, PICA_PROTO_NOTFOUND_SIZE, procmsg_NOTFOUND},
{PICA_PROTO_FOUND, PICA_MSG_FIXED_SIZE, PICA_PROTO_FOUND_SIZE, procmsg_FOUND},
{PICA_PROTO_CLNODELIST, PICA_MSG_VAR_SIZE, PICA_MSG_VARSIZE_INT16, procmsg_CLNODELIST},
{PICA_PROTO_PINGREQ, PICA_MSG_FIXED_SIZE, PICA_PROTO_PINGREQ_SIZE, procmsg_PINGREQ},
{PICA_PROTO_INITRESPOK, PICA_MSG_FIXED_SIZE, PICA_PROTO_INITRESPOK_SIZE, procmsg_INITRESP},
{PICA_PROTO_VERDIFFER, PICA_MSG_FIXED_SIZE, PICA_PROTO_VERDIFFER_SIZE, procmsg_INITRESP},
{PICA_PROTO_MULTILOGIN, PICA_MSG_VAR_SIZE, PICA_MSG_VARSIZE_INT16, procmsg_MULTILOGIN}
};
const struct PICA_msginfo c2c_messages[] =
{
{PICA_PROTO_MSGUTF8, PICA_MSG_VAR_SIZE, PICA_MSG_VARSIZE_INT16, procmsg_MSGUTF8},
{PICA_PROTO_MSGOK, PICA_MSG_FIXED_SIZE, PICA_PROTO_MSGOK_SIZE, procmsg_MSGOK},
{PICA_PROTO_CALLREQ, PICA_MSG_FIXED_SIZE, PICA_PROTO_CALLREQ_SIZE, procmsg_CALLREQ},
{PICA_PROTO_CALLANS, PICA_MSG_FIXED_SIZE, PICA_PROTO_CALLANS_SIZE, procmsg_CALLANS},
{PICA_PROTO_CALLREJ, PICA_MSG_FIXED_SIZE, PICA_PROTO_CALLREJ_SIZE, procmsg_CALLREJ},
{PICA_PROTO_CALLFIN, PICA_MSG_FIXED_SIZE, PICA_PROTO_CALLFIN_SIZE, procmsg_CALLFIN},
{PICA_PROTO_CALL_AUDIOPARAM, PICA_MSG_FIXED_SIZE, PICA_PROTO_CALL_AUDIOPARAM_SIZE, procmsg_CALL_AUDIOPARAM},
{PICA_PROTO_CALL_VIDEOPARAM, PICA_MSG_FIXED_SIZE, PICA_PROTO_CALL_VIDEOPARAM_SIZE, procmsg_CALL_VIDEOPARAM},
{PICA_PROTO_CALL_AUDIO_PACKET, PICA_MSG_VAR_SIZE, PICA_MSG_VARSIZE_INT16, procmsg_CALL_AUDIO_PACKET},
{PICA_PROTO_CALL_VIDEO_PACKET, PICA_MSG_VAR_SIZE, PICA_MSG_VARSIZE_INT16, procmsg_CALL_VIDEO_PACKET},
{PICA_PROTO_SENDFILEREQUEST, PICA_MSG_VAR_SIZE, PICA_MSG_VARSIZE_INT16, procmsg_SENDFILEREQUEST},
{PICA_PROTO_ACCEPTEDFILE, PICA_MSG_FIXED_SIZE, PICA_PROTO_ACCEPTEDFILE_SIZE, procmsg_ACCEPTEDFILE},
{PICA_PROTO_DENIEDFILE, PICA_MSG_FIXED_SIZE, PICA_PROTO_DENIEDFILE_SIZE, procmsg_DENIEDFILE},
{PICA_PROTO_FILEFRAGMENT, PICA_MSG_VAR_SIZE, PICA_MSG_VARSIZE_INT16, procmsg_FILEFRAGMENT},
{PICA_PROTO_FILECONTROL, PICA_MSG_FIXED_SIZE, PICA_PROTO_FILECONTROL_SIZE, procmsg_FILECONTROL},
{PICA_PROTO_DIRECTC2C_ADDRLIST, PICA_MSG_VAR_SIZE, PICA_MSG_VARSIZE_INT16, procmsg_PICA_PROTO_DIRECTC2C_ADDRLIST},
{PICA_PROTO_DIRECTC2C_FAILED, PICA_MSG_FIXED_SIZE, PICA_PROTO_DIRECTC2C_FAILED_SIZE, procmsg_PICA_PROTO_DIRECTC2C_FAILED},
{PICA_PROTO_DIRECTC2C_SWITCH, PICA_MSG_FIXED_SIZE, PICA_PROTO_DIRECTC2C_SWITCH_SIZE, procmsg_PICA_PROTO_DIRECTC2C_SWITCH},
{PICA_PROTO_C2CCONNREQ, PICA_MSG_FIXED_SIZE, PICA_PROTO_C2CCONNREQ_SIZE, procmsg_C2CCONNREQ},
{PICA_PROTO_INITRESPOK, PICA_MSG_FIXED_SIZE, PICA_PROTO_INITRESPOK_SIZE, procmsg_INITRESP_c2c},
{PICA_PROTO_VERDIFFER, PICA_MSG_FIXED_SIZE, PICA_PROTO_VERDIFFER_SIZE, procmsg_INITRESP_c2c}
};
struct PICA_msginfo c2c_init_messages[] =
{
};
int PICA_get_id_from_cert_file(const char *cert_file, unsigned char *id)
{
X509 *x;
FILE *f;
int ret;
f = fopen(cert_file, "r");
if (!f)
{
perror(cert_file);
return 0;
}
x = PEM_read_X509(f, 0, 0, 0);
fclose(f);
if (!x)
{
return 0;
}
ret = PICA_id_from_X509(x, id);
X509_free(x);
return ret;
}
int PICA_get_id_from_cert_string(const char *cert_pem, unsigned char *id)
{
X509 *x;
BIO *biomem;
biomem = BIO_new_mem_buf(cert_pem, -1);
x = PEM_read_bio_X509(biomem, NULL, 0, NULL);
if (!x)
{
return 0;
}
return PICA_id_from_X509(x, id);
}
static int verify_callback(int preverify_ok, X509_STORE_CTX *ctx)
{
//return 1 for self-signed certificates
char buf[256];
X509 *err_cert;
int err, depth;
err_cert = X509_STORE_CTX_get_current_cert(ctx);
err = X509_STORE_CTX_get_error(ctx);
depth = X509_STORE_CTX_get_error_depth(ctx);
X509_NAME_oneline(X509_get_subject_name(err_cert), buf, 256);
if (err == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT && depth == 0)
preverify_ok = 1;
if (!preverify_ok)
{
printf("verify error:num=%d:%s:depth=%d:%s\n", err,
X509_verify_cert_error_string(err), depth, buf);
}
return preverify_ok;
}
static int c2n_alloc_c2c(struct PICA_c2n *ci, struct PICA_c2c **chn, const unsigned char *peer_id, int is_outgoing)
{
struct PICA_c2c *chnl;
chnl = *chn = (struct PICA_c2c*)calloc(sizeof(struct PICA_c2c), 1);
if (!chnl)
return 0;//ERR_CHECK
chnl->conn = ci;
chnl->outgoing = is_outgoing;
memcpy(chnl->peer_id, peer_id, PICA_ID_SIZE);
chnl->acc = ci->acc;
chnl->timestamp = time(0);
chnl->sck_data = -1;//prevent closing stdin when closing non-active connection
chnl->read_buf = calloc(PICA_CHANREADBUFSIZE, 1);
if (ci->chan_list_head)
{
ci->chan_list_end->next = chnl;
chnl->prev = ci->chan_list_end;
ci->chan_list_end = chnl;
}
else
{
ci->chan_list_head = chnl;
ci->chan_list_end = chnl;
}
chnl->state = PICA_C2C_STATE_NEW;
chnl->directc2c_state = PICA_DIRECTC2C_STATE_INACTIVE;
return 1;
}
static int c2c_stage2_startanontls(struct PICA_c2c *chnl)
{
int ret;
PICA_TRACEFUNC
chnl->ssl = SSL_new(chnl->acc->anon_ctx);
if (!chnl->ssl)
return PICA_ERRSSL;
ret = SSL_set_fd(chnl->ssl, chnl->sck_data);
if (ret != 1)
return PICA_ERRSSL;
ret = SSL_connect(chnl->ssl);
ret = process_async_ssl_errors(chnl->ssl, ret, &chnl->want_write, 0);
if (ret != PICA_OK)
return ret;
chnl->state = PICA_C2C_STATE_WAITINGANONTLS;
return PICA_OK;
}
static int c2c_stage3_connid(struct PICA_c2c *chnl)
{
struct PICA_proto_msg *mp;
mp = c2c_writebuf_push(chnl, PICA_PROTO_CONNID, PICA_PROTO_CONNID_SIZE);
if (mp)
{
if (chnl->outgoing)
{
memcpy(mp->tail, chnl->acc->id, PICA_ID_SIZE);/* caller ID*/
memcpy(mp->tail + PICA_ID_SIZE, chnl->peer_id, PICA_ID_SIZE);/* callee ID*/
}
else
{
memcpy(mp->tail, chnl->peer_id, PICA_ID_SIZE);/* caller ID*/
memcpy(mp->tail + PICA_ID_SIZE, chnl->acc->id, PICA_ID_SIZE);/* callee ID*/
}
chnl->state = PICA_C2C_STATE_CONNID;
}
else
{
return PICA_ERRNOMEM;
}
return PICA_OK;
}
static int c2c_stage4_shutdownanontls(struct PICA_c2c *chnl)
{
int ret;
ret = SSL_shutdown(chnl->ssl);
if (ret < 0)
ret = process_async_ssl_errors(chnl->ssl, ret, &chnl->want_write, 0);
if (ret != PICA_OK)
return ret;
chnl->state = PICA_C2C_STATE_WAITINGANONTLSSHUTDOWN;
return PICA_OK;
}
static int c2c_stage5_starttls(struct PICA_c2c *chnl)
{
int ret;
SSL_free(chnl->ssl);
chnl->ssl = SSL_new(chnl->acc->ctx);
if (!chnl->ssl)
{
return PICA_ERRSSL;
}
ret = SSL_set_fd(chnl->ssl, chnl->sck_data);
if (!ret)
{
return PICA_ERRSSL;
}
SSL_set_verify(chnl->ssl, SSL_VERIFY_PEER, verify_callback);
SSL_set_verify_depth(chnl->ssl, 1);//peer certificate and one CA
if (chnl->outgoing)
ret = SSL_connect(chnl->ssl);
else
ret = SSL_accept(chnl->ssl);
if (ret == 0)
return PICA_ERRSSL;
if (ret < 0)
{
ret = SSL_get_error(chnl->ssl, ret);
if (ret != SSL_ERROR_WANT_READ && ret != SSL_ERROR_WANT_WRITE)
return PICA_ERRSSL;
}
chnl->state = PICA_C2C_STATE_WAITINGTLS;
return PICA_OK;
}
static int verify_peer_cert_common(X509 **peer_cert, SSL *ssl, const unsigned char *peer_id)
{
unsigned char idbuf[PICA_ID_SIZE];
EVP_PKEY *pubkey;
RSA *rsa;
*peer_cert = SSL_get_peer_certificate(ssl);
if (!*peer_cert)
return PICA_ERRNOPEERCERT;
pubkey = X509_get_pubkey(*peer_cert);
if (!pubkey)
return PICA_ERRNOPEERCERT;
rsa = EVP_PKEY_get1_RSA(pubkey);
#if OPENSSL_VERSION_NUMBER < 0x10100000L
if (!rsa || !rsa->n || BN_num_bits(rsa->n) < PICA_RSA_MINKEYSIZE)
#else
if (!rsa || RSA_bits(rsa) < PICA_RSA_MINKEYSIZE)
#endif
return PICA_ERRNOPEERCERT;
RSA_free(rsa);
EVP_PKEY_free(pubkey);
if (PICA_id_from_X509(*peer_cert, idbuf) == 0)
{
return PICA_ERRNOPEERCERT;
}
if (memcmp(idbuf, peer_id, PICA_ID_SIZE) != 0)
{
return PICA_ERRINVPEERCERT;
}
{
BIO *mem = BIO_new(BIO_s_mem());
char *cert_pem;
unsigned int cert_pem_nb;
PEM_write_bio_X509(mem, *peer_cert);
cert_pem_nb = (unsigned int)BIO_get_mem_data(mem, &cert_pem);
if (callbacks.peer_cert_verify_cb(peer_id, (const char*)cert_pem, cert_pem_nb) == 0)
{
BIO_free(mem);
return PICA_ERRINVPEERCERT;
}
BIO_free(mem);
}
return PICA_OK;
}
static int c2c_verify_peer_cert(struct PICA_c2c *chnl)
{
return verify_peer_cert_common(&chnl->peer_cert, chnl->ssl, chnl->peer_id);
}
static int c2c_stage6_sendc2cconnreq(struct PICA_c2c *chnl)
{
struct PICA_proto_msg *mp;
mp = c2c_writebuf_push(chnl, PICA_PROTO_C2CCONNREQ, PICA_PROTO_C2CCONNREQ_SIZE);
if (!mp)
return PICA_ERRNOMEM;
mp->tail[0] = PICA_C2CPROTO_VER_HIGH;
mp->tail[1] = PICA_C2CPROTO_VER_LOW;
chnl->state = PICA_C2C_STATE_WAITINGREP;
return PICA_OK;
}
static int c2c_start(struct PICA_c2c *chnl)
{
int ret, err_ret;
chnl->sck_data = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
IOCTLSETNONBLOCKINGSOCKET(chnl->sck_data, 1);
{
struct sockaddr_in addr = chnl->conn->srv_addr;
addr.sin_port = htons(ntohs(addr.sin_port));
chnl->state = PICA_C2C_STATE_CONNECTING;
ret = connect(chnl->sck_data, (struct sockaddr*)&addr, sizeof(struct sockaddr_in));
if (ret == 0)
{
ret = c2c_stage2_startanontls(chnl);
if (ret != PICA_OK)
{
err_ret = ret;
goto error_ret;
}
return PICA_OK;
}
if ((err_ret = process_first_async_connect_result(ret)) != PICA_OK)
goto error_ret;
}
return PICA_OK;
error_ret:
callbacks.c2c_failed_cb(chnl->peer_id);
PICA_close_c2c(chnl);
return err_ret;
}
static unsigned int procmsg_C2CCONNREQ(unsigned char* buf, unsigned int nb, void* p)
{
struct PICA_c2c *cc = (struct PICA_c2c *)p;
struct PICA_proto_msg *mp;
if (cc->state != PICA_C2C_STATE_WAITINGC2CPROTOVER)
return 0;
if (buf[2] == PICA_C2CPROTO_VER_HIGH && buf[3] == PICA_C2CPROTO_VER_LOW)
{
//send ok
mp = c2c_writebuf_push(cc, PICA_PROTO_INITRESPOK, PICA_PROTO_INITRESPOK_SIZE);
if (!mp)
return 0;
mp->tail[0] = 'O';
mp->tail[1] = 'K';
cc->state = PICA_C2C_STATE_ACTIVE;
callbacks.c2c_established_cb(cc->peer_id, SSL_get_cipher_name(cc->ssl));
if (cc->conn->directc2c_config == PICA_DIRECTC2C_CFG_ALLOWINCOMING)
PICA_send_directc2caddrlist(cc);
}
else
{
//send verdiffer, disconnect after sending
mp = c2c_writebuf_push(cc, PICA_PROTO_VERDIFFER, PICA_PROTO_VERDIFFER_SIZE);
if (!mp)
return 0;
mp->tail[0] = PICA_C2CPROTO_VER_HIGH;
mp->tail[1] = PICA_C2CPROTO_VER_LOW;
cc->disconnect_on_empty_write_buf = 1;
}
return 1;
}
static unsigned int procmsg_INITRESP_c2c(unsigned char* buf, unsigned int nb, void* p)
{
struct PICA_c2c *cc = (struct PICA_c2c *)p;
if (cc->state != PICA_C2C_STATE_WAITINGREP)
return 0;
if (!cc->outgoing)
return 0;
switch(buf[0])
{
case PICA_PROTO_INITRESPOK:
cc->state = PICA_C2C_STATE_ACTIVE;
callbacks.c2c_established_cb(cc->peer_id, SSL_get_cipher_name(cc->ssl));
if (cc->conn->directc2c_config == PICA_DIRECTC2C_CFG_ALLOWINCOMING)
PICA_send_directc2caddrlist(cc);
break;
case PICA_PROTO_VERDIFFER:
return 0; // -- ???
break;
default:
return 0;
}
return 1;
}
static int directc2c_connect_next(struct PICA_directc2c *dc2c, struct PICA_c2c *c2c)
{
PICA_TRACEFUNC
int ret;
if (dc2c->ssl)
SSL_free(dc2c->ssl);
dc2c->ssl = NULL;
if (dc2c->sck >= 0)
CLOSE(dc2c->sck);
dc2c->sck = -1;
if (dc2c->addrpos == 0)
dc2c->addrpos += 2;
if (dc2c->addrpos >= *(uint16_t*)dc2c->addrlist)
return PICA_ERRNOTFOUND;
switch(dc2c->addrlist[dc2c->addrpos])
{
case PICA_PROTO_DIRECTC2C_IPV4:
dc2c->sck = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
IOCTLSETNONBLOCKINGSOCKET(dc2c->sck, 1);
memset(&dc2c->addr, 0, sizeof(dc2c->addr));
dc2c->addr.sin_family = AF_INET;
dc2c->addr.sin_addr.s_addr = *((in_addr_t*)(dc2c->addrlist + dc2c->addrpos + 1));
dc2c->addr.sin_port = *((uint16_t*)(dc2c->addrlist + dc2c->addrpos + 5));
printf("received direct IPv4 addr %s port %i\n", inet_ntoa(dc2c->addr.sin_addr), ntohs(dc2c->addr.sin_port));
dc2c->state = PICA_DIRECTC2C_CONNSTATE_CONNECTING;
ret = connect(dc2c->sck, (struct sockaddr*)&dc2c->addr, sizeof(dc2c->addr));
ret = process_first_async_connect_result(ret);
if (ret != PICA_OK)
dc2c->state = PICA_DIRECTC2C_CONNSTATE_FAILED;
dc2c->addrpos += PICA_PROTO_DIRECTC2C_ADDRLIST_ITEM_IPV4_SIZE;
break;
case PICA_PROTO_DIRECTC2C_IPV6:
break;
}
return PICA_OK;
}
static unsigned int procmsg_PICA_PROTO_DIRECTC2C_ADDRLIST(unsigned char* buf, unsigned int nb, void* p)
{
PICA_TRACEFUNC
struct PICA_c2c *cc = (struct PICA_c2c *)p;
int ret;
if (cc->conn->directc2c_config != PICA_DIRECTC2C_CFG_DISABLED)
{
if (cc->directc2c_state != PICA_DIRECTC2C_STATE_INACTIVE && cc->directc2c_state != PICA_DIRECTC2C_STATE_WAITINGINCOMING)
return 0;
if (cc->outgoing == PICA_C2C_OUTGOING || cc->conn->directc2c_config == PICA_DIRECTC2C_CFG_CONNECTONLY)
cc->directc2c_state = PICA_DIRECTC2C_STATE_CONNECTING;
cc->direct = calloc(1, sizeof(struct PICA_directc2c));
if (!cc->direct)
return 1;
cc->direct->is_outgoing = PICA_DIRECTC2C_OUTGOING;
cc->direct->state = PICA_DIRECTC2C_CONNSTATE_NEW;
cc->direct->sck = -1;
cc->direct->addrlist = malloc(nb - 2 + 1);
if (!cc->direct->addrlist)
return 1;
memcpy(cc->direct->addrlist, buf + 2, nb - 2);// copy size and address list
cc->direct->addrlist[nb - 2] = 0;
cc->direct->addrpos = 0;
if (cc->outgoing == PICA_C2C_INCOMING && cc->conn->directc2c_config == PICA_DIRECTC2C_CFG_ALLOWINCOMING)
return 1;//connect later, if no incoming connection is accepted and DIRECTC2C_FAILED is received
ret = directc2c_connect_next(cc->direct, cc);
if (ret == PICA_ERRNOTFOUND)
{
cc->directc2c_state = PICA_DIRECTC2C_STATE_FAILEDTOCONNECT;
}
}
return 1;
}
static unsigned int procmsg_PICA_PROTO_DIRECTC2C_FAILED(unsigned char* buf, unsigned int nb, void* p)
{
PICA_TRACEFUNC
struct PICA_c2c *cc = (struct PICA_c2c *)p;
int ret;
if (cc->directc2c_state != PICA_DIRECTC2C_STATE_WAITINGINCOMING)
return 0;
if (cc->conn->directc2c_config == PICA_DIRECTC2C_CFG_DISABLED)
return 0;
if (cc->outgoing != PICA_C2C_INCOMING)
return 0;
ret = directc2c_connect_next(cc->direct, cc);
if (ret == PICA_ERRNOTFOUND)
{
cc->directc2c_state = PICA_DIRECTC2C_STATE_FAILEDTOCONNECT;
}
cc->directc2c_state = PICA_DIRECTC2C_STATE_CONNECTING;
return 1;
}
unsigned int procmsg_PICA_PROTO_DIRECTC2C_SWITCH(unsigned char* buf, unsigned int nb, void* p)
{
PICA_TRACEFUNC
struct PICA_c2c *cc = (struct PICA_c2c *)p;
int ret;
if (cc->conn->directc2c_config == PICA_DIRECTC2C_CFG_DISABLED)
return 0;
if (cc->directc2c_state != PICA_DIRECTC2C_STATE_ACTIVE && cc->directc2c_state != PICA_DIRECTC2C_STATE_CONNECTING)
return 0;
cc->switched_to_directc2c_read = 1;
return 1;
}
static unsigned int procmsg_INITRESP(unsigned char* buf, unsigned int nb, void* p)
{
struct PICA_c2n *ci = (struct PICA_c2n *)p;
if (ci->state != PICA_C2N_STATE_WAITINGREP)
return 0;
switch(buf[0])
{
case PICA_PROTO_INITRESPOK:
ci -> init_resp_ok = 1;
break;
case PICA_PROTO_VERDIFFER:
ci->init_resp_ok = -1;
ci->node_ver_major = buf[2];
ci->node_ver_minor = buf[3];
break;
default:
return 0;
}
return 1;
}
static unsigned int procmsg_CONNREQINC(unsigned char* buf, unsigned int nb, void* p)
{
int ret;
struct PICA_c2n *ci = (struct PICA_c2n *)p;
struct PICA_proto_msg *mp;
unsigned char *peer_id = buf + 2;
if (callbacks.accept_cb(peer_id))
{
struct PICA_c2c *_chnl;
mp = c2n_writebuf_push( ci, PICA_PROTO_CONNALLOW, PICA_PROTO_CONNALLOW_SIZE);
if (mp)
{
memcpy(mp->tail, peer_id, PICA_ID_SIZE);
}
else
return 0;
ret = c2n_alloc_c2c( ci, &_chnl, peer_id, PICA_C2C_INCOMING);
if (!ret)
return 0;
do
{
ret = PICA_write_c2n(ci);
if (PICA_OK != ret)
return 0;
}
while( ci->write_pos > 0);
ret = c2c_start(_chnl);
if (ret != PICA_OK)
return 1;
return 1;
}
else
{
mp = c2n_writebuf_push(ci, PICA_PROTO_CONNDENY, PICA_PROTO_CONNDENY_SIZE);
if (mp)
{
memcpy(mp->tail, peer_id, PICA_ID_SIZE);
}
else
return 0;//ERR_CHECK
}
return 1;
}
static unsigned int procmsg_NOTFOUND(unsigned char* buf, unsigned int nb, void* p)
{
struct PICA_c2n *ci = (struct PICA_c2n *)p;
struct PICA_c2c *ipt, *rq = 0;
unsigned char *peer_id = buf + 2;
//puts("procmsg_NOTFOUND");//debug
ipt = ci->chan_list_end;
while(ipt)
{
if (memcmp(ipt->peer_id, peer_id, PICA_ID_SIZE) == 0)
{
rq = ipt;
break;
}
ipt = ipt->prev;
}
if (!rq)
return 0;// ERR_CHECK -левое сообщение, такой запрос не посылался
//puts("procmsg_NOTFOUND_chkp0");//debug
PICA_close_c2c(rq);
//puts("procmsg_NOTFOUND_chkp1");//debug
callbacks.notfound_cb(peer_id);
return 1;
}
static unsigned int procmsg_FOUND(unsigned char* buf, unsigned int nb, void* p)
{
struct PICA_c2n *ci = (struct PICA_c2n *)p;
struct PICA_c2c *ipt, *rq = 0;
unsigned char *peer_id = buf + 2;
int ret;
ipt = ci->chan_list_end;
while(ipt)
{
if (memcmp(ipt->peer_id, peer_id, PICA_ID_SIZE) == 0)
{
rq = ipt;
break;
}
ipt = ipt->prev;
}
if (!rq)
return 0;// ERR_CHECK -левое сообщение, такой запрос не посылался
ret = c2c_start(rq);
if (ret != PICA_OK)
return 0;
return 1;
}
static unsigned int procmsg_CLNODELIST(unsigned char* buf, unsigned int nb, void* p)
{
PICA_TRACEFUNC
unsigned int pos = 4;
char ipaddr_string[INET6_ADDRSTRLEN];
char temp_zeroswap;
unsigned int port;
//puts("CLNODELIST");//debug
//callbacks.nodelist_cb(buf + 4, nb - 4);
do
{
switch(buf[pos])
{
case PICA_PROTO_NEWNODE_IPV4:
callbacks.nodelist_cb(
PICA_PROTO_NEWNODE_IPV4,
buf + pos + 1,
inet_ntop(AF_INET, buf + pos + 1, ipaddr_string, INET6_ADDRSTRLEN),
ntohs(*(uint16_t*)(buf + pos + 5))
);
pos += PICA_PROTO_NODELIST_ITEM_IPV4_SIZE;
break;
case PICA_PROTO_NEWNODE_IPV6:
callbacks.nodelist_cb(
PICA_PROTO_NEWNODE_IPV6,
buf + pos + 1,
inet_ntop(AF_INET6, buf + pos + 1, ipaddr_string, INET6_ADDRSTRLEN),
ntohs(*(uint16_t*)(buf + pos + 17))
);
pos += PICA_PROTO_NODELIST_ITEM_IPV6_SIZE;
break;
case PICA_PROTO_NEWNODE_DNS:
port = ntohs(*(uint16_t*)(buf + pos + buf[pos + 1] + 2));
temp_zeroswap = buf[pos + 2 + buf[pos + 1]]; //save byte after hostname string
buf[pos + 2 + buf[pos + 1]] = '\0'; // and replace it with string terminating zero
callbacks.nodelist_cb(
PICA_PROTO_NEWNODE_DNS,
buf + pos + 2,
buf + pos + 2,
port
);
buf[pos + 2 + buf[pos + 1]] = temp_zeroswap;
pos += 4 + buf[pos + 1];
break;
default:
//puts("unknown node address type");//debug
return 0;
}
}
while(pos < nb);
return 1;
}
static unsigned int procmsg_MSGUTF8(unsigned char* buf, unsigned int nb, void* p)
{
struct PICA_c2c *chan = (struct PICA_c2c *)p;
struct PICA_proto_msg *mp;
if (chan->state != PICA_C2C_STATE_ACTIVE)
return 0;
callbacks.newmsg_cb(chan->peer_id, buf + 4, nb - 4, buf[0]);
//send PICA_PROTO_MSGOK
mp = c2c_writebuf_push( chan, PICA_PROTO_MSGOK, PICA_PROTO_MSGOK_SIZE);
if (mp)
{
RAND_pseudo_bytes( mp->tail, 2);
}
else
return 0;
return 1;
}
static unsigned int procmsg_MSGOK(unsigned char* buf, unsigned int nb, void* p)
{
struct PICA_c2c *chan = (struct PICA_c2c *)p;
callbacks.msgok_cb(chan -> peer_id);
return 1;
}
unsigned int procmsg_PINGREQ(unsigned char* buf, unsigned int nb, void* p)
{
PICA_TRACEFUNC
struct PICA_c2n *ci = (struct PICA_c2n *)p;
struct PICA_proto_msg *mp;
mp = c2n_writebuf_push(ci, PICA_PROTO_PINGREP, PICA_PROTO_PINGREP_SIZE);
if (mp)
{
RAND_pseudo_bytes(mp->tail, 2);
}
else
return 0;
return 1;
}
int PICA_deny_file(struct PICA_c2c *chan)
{
struct PICA_proto_msg *mp;
if (chan->recvfilestate != PICA_CHANRECVFILESTATE_WAITACCEPT)
return PICA_ERRINVARG;
chan->recvfilestate = PICA_CHANRECVFILESTATE_IDLE;
mp = c2c_writebuf_push(chan, PICA_PROTO_DENIEDFILE, PICA_PROTO_DENIEDFILE_SIZE);
if (mp)
{
RAND_pseudo_bytes(mp->tail, 2);
}
else
return PICA_ERRNOMEM;
return PICA_OK;
}
int PICA_accept_file(struct PICA_c2c *chan, char *filename, unsigned int filenamesize)
{
struct PICA_proto_msg *mp;
int ret;
if (chan->recvfilestate != PICA_CHANRECVFILESTATE_WAITACCEPT)
return PICA_ERRINVARG;
//open file
ret = PICA_recvfile_open_write(chan, filename, filenamesize);
if (ret != PICA_OK)
return ret;
mp = c2c_writebuf_push(chan, PICA_PROTO_ACCEPTEDFILE, PICA_PROTO_ACCEPTEDFILE_SIZE);
if (mp)
{
RAND_pseudo_bytes(mp->tail, 2);
}
else
return PICA_ERRNOMEM;
chan->recvfilestate = PICA_CHANRECVFILESTATE_RECEIVING;
return PICA_OK;
}
static int validate_file_name(const char *buf, unsigned int len)
{
int ret = PICA_OK;
char *str = malloc(len + 1);
memcpy(str, buf, len);
str[len] = '\0';
/* TODO Unicode canonicalization */
if (strchr(str, '*') != NULL
|| strchr(str, ':') != NULL
|| strchr(str, '/') != NULL
|| strstr(str, "..") != NULL)
ret = PICA_ERRINVARG;
free(str);
return ret;
}
static unsigned int procmsg_SENDFILEREQUEST(unsigned char* buf, unsigned int nb, void* p)
{
struct PICA_c2c *chan = (struct PICA_c2c *)p;
struct PICA_proto_msg *mp;
int ret;
char *filename = buf + 12;
unsigned int filenamesize = *(uint16_t*)(buf + 2) - 8;
if (chan->recvfilestate != PICA_CHANRECVFILESTATE_IDLE)
return 0;
if (validate_file_name(filename, filenamesize) != PICA_OK)
return 0;
chan->recvfilestate = PICA_CHANRECVFILESTATE_WAITACCEPT;
chan->recvfile_size = *(uint64_t*)(buf + 4);
chan->recvfile_pos = 0;
ret = callbacks.accept_file_cb(chan->peer_id, *(uint64_t*)(buf + 4), filename, filenamesize);
if (ret > 0)
{
if (ret == 1)
{
if (PICA_accept_file(chan, filename, filenamesize) != PICA_OK)
if (PICA_deny_file(chan) != PICA_OK)
return 0;
}
}
else
{
if (PICA_deny_file(chan) != PICA_OK)
return 0;
}
return 1;
}
static unsigned int procmsg_ACCEPTEDFILE(unsigned char* buf, unsigned int nb, void* p)
{
struct PICA_c2c *chan = (struct PICA_c2c *)p;
if (chan->sendfilestate != PICA_CHANSENDFILESTATE_SENTREQ)
return 0;
callbacks.accepted_file_cb(chan->peer_id);
chan->sendfilestate = PICA_CHANSENDFILESTATE_SENDING;
return 1;
}
static unsigned int procmsg_DENIEDFILE(unsigned char* buf, unsigned int nb, void* p)
{
struct PICA_c2c *chan = (struct PICA_c2c *)p;
if (chan->sendfilestate != PICA_CHANSENDFILESTATE_SENTREQ)
return 0;
callbacks.denied_file_cb(chan->peer_id);
chan->sendfilestate = PICA_CHANSENDFILESTATE_IDLE;
fclose(chan->sendfile_stream);
chan->sendfile_stream = NULL;
return 1;
}
static unsigned int procmsg_FILEFRAGMENT(unsigned char* buf, unsigned int nb, void* p)
{
struct PICA_c2c *chan = (struct PICA_c2c *)p;
/*
*Allow incoming FILEFRAGMENTs while paused
*/
if (chan->recvfilestate != PICA_CHANRECVFILESTATE_RECEIVING
&& chan->recvfilestate != PICA_CHANRECVFILESTATE_PAUSED)
return 0;
if (nb - 4 != *(uint16_t*)(buf + 2))
return 0;
if (fwrite(buf + 4, nb - 4, 1, chan->recvfile_stream) != 1)
{
struct PICA_proto_msg *mp;
fclose(chan->recvfile_stream);
chan->recvfile_stream = NULL;
chan->recvfilestate = PICA_CHANRECVFILESTATE_IDLE;
if (PICA_OK != PICA_send_filecontrol(chan, PICA_PROTO_FILECONTROL_VOID, PICA_PROTO_FILECONTROL_IOERROR))
return 0;
}
chan->recvfile_pos += (nb - 4);
if (chan->recvfile_pos > chan->recvfile_size) //received more than file_size
return 0;
callbacks.file_progress_cb(chan->peer_id, 0, chan->recvfile_pos);
if (chan->recvfile_pos == chan->recvfile_size)
{
chan->recvfilestate = PICA_CHANRECVFILESTATE_IDLE;
fclose(chan->recvfile_stream);
chan->recvfile_stream = NULL;
callbacks.file_finished_cb(chan->peer_id, 0);
}
return 1;
}
static unsigned int procmsg_FILECONTROL(unsigned char* buf, unsigned int nb, void* p)
{
struct PICA_c2c *chan = (struct PICA_c2c *)p;
unsigned int sender_cmd, receiver_cmd;
sender_cmd = buf[2];
receiver_cmd = buf[3];
if ((chan->sendfilestate == PICA_CHANSENDFILESTATE_IDLE || chan->sendfilestate == PICA_CHANSENDFILESTATE_SENTREQ)
&& receiver_cmd != PICA_PROTO_FILECONTROL_VOID)
return 0;
if (chan->recvfilestate == PICA_CHANRECVFILESTATE_IDLE && sender_cmd != PICA_PROTO_FILECONTROL_VOID)
return 0;
switch(sender_cmd)
{
case PICA_PROTO_FILECONTROL_PAUSE:
if (chan->recvfilestate == PICA_CHANRECVFILESTATE_RECEIVING)
chan->recvfilestate = PICA_CHANRECVFILESTATE_PAUSED;
else
return 0;
break;
case PICA_PROTO_FILECONTROL_RESUME:
if (chan->recvfilestate == PICA_CHANRECVFILESTATE_PAUSED)
chan->recvfilestate = PICA_CHANRECVFILESTATE_RECEIVING;
else
return 0;
break;
case PICA_PROTO_FILECONTROL_CANCEL:
case PICA_PROTO_FILECONTROL_IOERROR:
chan->recvfilestate = PICA_CHANRECVFILESTATE_IDLE;
fclose(chan->recvfile_stream);
chan->recvfile_stream = NULL;
break;
case PICA_PROTO_FILECONTROL_VOID:
break;
default:
return 0;
}
switch(receiver_cmd)
{
case PICA_PROTO_FILECONTROL_PAUSE:
if (chan->sendfilestate == PICA_CHANSENDFILESTATE_SENDING)
chan->sendfilestate = PICA_CHANSENDFILESTATE_PAUSED;
else
return 0;
break;
case PICA_PROTO_FILECONTROL_RESUME:
if (chan->sendfilestate == PICA_CHANSENDFILESTATE_PAUSED)
chan->sendfilestate = PICA_CHANSENDFILESTATE_SENDING;
else
return 0;
break;
case PICA_PROTO_FILECONTROL_CANCEL:
case PICA_PROTO_FILECONTROL_IOERROR:
chan->sendfilestate = PICA_CHANSENDFILESTATE_IDLE;
fclose(chan->sendfile_stream);
chan->sendfile_stream = NULL;
break;
case PICA_PROTO_FILECONTROL_VOID:
break;
default:
return 0;
}
callbacks.file_control_cb(chan->peer_id, sender_cmd, receiver_cmd);
return 1;
}
#ifdef PICA_MULTITHREADED
#ifdef WIN32
void locking_cb(int mode, int type, char *file, int line)
{
if (mode & CRYPTO_LOCK)
WaitForSingleObject(mt_locks[type], INFINITE);
else
ReleaseMutex(mt_locks[type]);
}
#else
unsigned long thread_id_cb(void)
{
return (unsigned long)pthread_self();
}
void locking_cb(int mode, int type, char *file, int line)
{
if (mode & CRYPTO_LOCK)
pthread_mutex_lock(&mt_locks[type]);
else
pthread_mutex_unlock(&mt_locks[type]);
}
#endif
#endif
int PICA_client_init(struct PICA_client_callbacks *clcbs)
{
struct sockaddr_in sd;
#ifdef WIN32
WSADATA wsd;
WSAStartup(MAKEWORD(2, 2), &wsd);
#endif
if (!clcbs)
return 0;
callbacks = *clcbs;
SSL_load_error_strings();
SSL_library_init();
#ifdef NO_RAND_DEV
PICA_rand_seed();
#endif
#ifdef PICA_MULTITHREADED
{
int i;
#ifdef WIN32
mt_locks = malloc(CRYPTO_num_locks() * sizeof(HANDLE));
for (i = 0; i < CRYPTO_num_locks(); i++)
mt_locks[i] = CreateMutex(NULL, FALSE, NULL);
CRYPTO_set_locking_callback(locking_cb);
#else
mt_locks = malloc(CRYPTO_num_locks() * sizeof(pthread_mutex_t));
memset(mt_locks, 0, CRYPTO_num_locks()*sizeof(pthread_mutex_t));
for (i = 0; i < CRYPTO_num_locks(); i++)
pthread_mutex_init(&mt_locks[i], NULL);
CRYPTO_set_id_callback(thread_id_cb);
CRYPTO_set_locking_callback(locking_cb);
#endif
}
#endif
//ctx=SSL_CTX_new(TLSv1_method());
//if (!ctx)
//;//ERR_CHECK
#ifndef WIN32
signal(SIGPIPE, SIG_IGN);
#endif
return 1;
}
static int check_pkey_passphrase(const char *pkey_file,
int (*password_cb)(char *buf, int size, int rwflag, void *userdata),
void *userdata)
{
RSA *rsa;
FILE *f;
f = fopen(pkey_file, "r");
if (!f)
return PICA_ERRINVPKEYFILE;
rsa = PEM_read_RSAPrivateKey(f, NULL, password_cb, userdata);
fclose(f);
if (!rsa)
{
unsigned long int e = ERR_get_error();
// 0x6065064 is an OpenSSL error code corresponding to decryption failure
if (e == 0x6065064 || strcmp("bad decrypt", ERR_reason_error_string(e)) == 0)
return PICA_ERRINVPKEYPASSPHRASE;
return PICA_ERRINVPKEYFILE;
}
RSA_free(rsa);
return PICA_OK;
}
int PICA_new_listener(const struct PICA_acc *acc, const char *public_addr, int public_port, int local_port, struct PICA_listener **l)
{
int ret, ret_err, flag;
struct PICA_listener *lst = NULL;
struct sockaddr_in s;
if (!acc || !public_addr || public_port <= 0 || public_port > 65535 || local_port <= 0 || local_port > 65535 || !l)
return PICA_ERRINVARG;
lst = *l = (struct PICA_listener*)calloc(sizeof(struct PICA_listener), 1);
if (!lst)
return PICA_ERRNOMEM;
lst->public_addr_dns = strdup(public_addr);
if (!lst->public_addr_dns)
{
ret_err = PICA_ERRNOMEM;
goto error_ret_0;
}
lst->public_addr_ipv4 = inet_addr(public_addr);
lst->acc = acc;
lst->public_port = public_port;
lst->local_port = local_port;
lst->sck_listener = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); //(1)
if (lst->sck_listener == SOCKET_ERROR)
{
ret_err = PICA_ERRSOCK;
goto error_ret_1;
}
flag = 1;
setsockopt(lst->sck_listener, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag));
IOCTLSETNONBLOCKINGSOCKET(lst->sck_listener, 1);
memset(&s, 0, sizeof(s));
s.sin_family = AF_INET;
s.sin_addr.s_addr = INADDR_ANY;
s.sin_port = htons(local_port);
ret = bind(lst->sck_listener, (const struct sockaddr*) &s, sizeof(s));//(2)
if (ret == SOCKET_ERROR)
{
ret_err = PICA_ERRSOCK;
goto error_ret_2;
}
ret = listen(lst->sck_listener, 10);
if (ret == SOCKET_ERROR)
{
ret_err = PICA_ERRSOCK;
goto error_ret_2;
}
return PICA_OK;
error_ret_2: //(2)
CLOSE(lst->sck_listener);
error_ret_1: //(1)
free(lst->public_addr_dns);
error_ret_0: //(0)
free(lst);
*l = 0;
return ret_err;
}
int PICA_open_acc(const char *cert_file,
const char *pkey_file,
const char *dh_param_file,
int (*password_cb)(char *buf, int size, int rwflag, void *userdata),
struct PICA_acc **acc)
{
int ret, ret_err;
DH *dh = NULL;
FILE *dh_file = NULL;
struct PICA_acc *a = NULL;
if (!(cert_file && pkey_file && dh_param_file && acc))
return PICA_ERRINVARG;
a = *acc = (struct PICA_acc*)calloc(sizeof(struct PICA_acc), 1);//(1)
if (!a)
return PICA_ERRNOMEM;
a->pkey_file = strdup(pkey_file);
a->cert_file = strdup(cert_file);
a->password_cb = password_cb;
a->ctx = SSL_CTX_new(TLSv1_2_method());//(2)
if (!a->ctx)
{
ret_err = PICA_ERRSSL;
goto error_ret_1;
}
a->anon_ctx = SSL_CTX_new(TLSv1_2_method());//(3)
if (!a->anon_ctx)
{
ret_err = PICA_ERRSSL;
goto error_ret_2;
}
/* Available since openssl 1.1.0
SSL_CTX_set_security_level(a->ctx, PICA_OPENSSL_SECURUTY_LEVEL);
*/
dh_file = fopen(dh_param_file, "r");
if (dh_file)
{
dh = PEM_read_DHparams(dh_file, NULL, NULL, NULL);
fclose(dh_file);
}
else
{
ret_err = PICA_ERRINVARG;
goto error_ret_3;
}
if (1 != SSL_CTX_set_tmp_dh(a->ctx, dh) || 1 != SSL_CTX_set_tmp_dh(a->anon_ctx, dh))
{
ret_err = PICA_ERRSSL;
goto error_ret_3;
}
DH_free(dh);
SSL_CTX_set_options(a->ctx, SSL_OP_SINGLE_DH_USE);
SSL_CTX_set_options(a->anon_ctx, SSL_OP_SINGLE_DH_USE);
if (!PICA_get_id_from_cert_file(cert_file, a->id))
{
ret_err = PICA_ERRINVCERT;
goto error_ret_3;
}
ret = check_pkey_passphrase(pkey_file, password_cb, a->id);
if (ret != PICA_OK)
{
ret_err = ret;
goto error_ret_3;
}
if (password_cb)
{
SSL_CTX_set_default_passwd_cb(a->ctx, password_cb);
SSL_CTX_set_default_passwd_cb_userdata(a->ctx, a->id);
}
ret = SSL_CTX_use_certificate_file(a->ctx, cert_file, SSL_FILETYPE_PEM);
if (ret != 1)
{
ret_err = PICA_ERRSSL;
goto error_ret_3;
}
ret = SSL_CTX_use_PrivateKey_file(a->ctx, pkey_file, SSL_FILETYPE_PEM);
if (ret != 1)
{
ret_err = PICA_ERRSSL;
goto error_ret_3;
}
ret = SSL_CTX_set_cipher_list(a->ctx, PICA_TLS_CIPHERLIST);
if (ret == 1)
#if OPENSSL_VERSION_NUMBER < 0x10100000L
ret = SSL_CTX_set_cipher_list(a->anon_ctx, PICA_TLS_ANONDHCIPHERLIST);
#else
ret = SSL_CTX_set_cipher_list(a->anon_ctx, PICA_TLS_ANONDHCIPHERLIST "@SECLEVEL=0");
#endif
if (ret != 1)
{
ret_err = PICA_ERRSSL;
goto error_ret_3;
}
return PICA_OK;
error_ret_3:
SSL_CTX_free(a->anon_ctx);//(3)
error_ret_2: //(2)
SSL_CTX_free(a->ctx);
error_ret_1: //(1)
free(a->pkey_file);
free(a->cert_file);
free(a);
*acc = 0;
return ret_err;
}
static int c2n_stage2_startanontls(struct PICA_c2n *c2n)
{
int ret;
ret = SSL_set_fd(c2n->ssl_comm, c2n->sck_comm);
if (ret != 1)
return PICA_ERRSSL;
ret = SSL_connect(c2n->ssl_comm);
ret = process_async_ssl_errors(c2n->ssl_comm, ret, &c2n->want_write, 0);
if (ret != PICA_OK)
return ret;
c2n->state = PICA_C2N_STATE_WAITINGANONTLS;
return PICA_OK;
}
static int c2n_stage3_sendreq(struct PICA_c2n *c2n)
{
PICA_TRACEFUNC
struct PICA_proto_msg *mp;
puts("c2n_stage3_sendreq");//debug
mp = c2n_writebuf_push(c2n, PICA_PROTO_INITREQ, PICA_PROTO_INITREQ_SIZE);
if (mp)
{
mp->tail[0] = PICA_PROTO_VER_HIGH;
mp->tail[1] = PICA_PROTO_VER_LOW;
}
else
return PICA_ERRNOMEM;
c2n->state = PICA_C2N_STATE_WAITINGREP;
return PICA_OK;
}
static int c2n_stage4_shutdownanontls(struct PICA_c2n *c2n)
{
int ret;
ret = SSL_shutdown(c2n->ssl_comm);
if (ret < 0)
ret = process_async_ssl_errors(c2n->ssl_comm, ret, &c2n->want_write, 0);
if (ret != PICA_OK)
return ret;
c2n->state = PICA_C2N_STATE_WAITINGANONTLSSHUTDOWN;
return PICA_OK;
}
static int c2n_stage5_starttls(struct PICA_c2n *c2n)
{
PICA_TRACEFUNC
int ret;
/* Free anon ssl struct */
SSL_free(c2n->ssl_comm);
c2n->ssl_comm = SSL_new(c2n->acc->ctx);
if (!c2n->ssl_comm)
return PICA_ERRSSL;
ret = SSL_set_fd(c2n->ssl_comm, c2n->sck_comm);
if (ret != 1)
return PICA_ERRSSL;
ret = SSL_accept(c2n->ssl_comm);
if (ret == 0)
return PICA_ERRSSL;
if (ret < 0)
ret = process_async_ssl_errors(c2n->ssl_comm, ret, &c2n->want_write, 0);
if (ret != PICA_OK)
return ret;
c2n->state = PICA_C2N_STATE_WAITINGTLS;
return PICA_OK;
}
static unsigned int procmsg_MULTILOGIN(unsigned char *buf, unsigned int nb, void *p)
{
struct PICA_c2n *c2n = (struct PICA_c2n *) p;
void *sigdatas[4];
size_t sigdatalengths[4];
uint64_t timestamp;
FILE *f;
EVP_PKEY *pubkey;
X509 *x;
int ret = 0;
uint16_t payload_len = *(uint16_t*)(buf + 2);
int siglen;
unsigned char *sig;
PICA_TRACEFUNC
//TODO report invalid MULTILOGIN messages
if (payload_len <= sizeof timestamp + PICA_PROTO_NODELIST_ITEM_IPV4_SIZE)
{
fprintf(stderr, "Invalid payload size of MULTILOGIN message: %u\n", payload_len);
return 1;
}
timestamp = *(uint64_t*)(buf + 4);
if (timestamp <= c2n->multilogin_last_timestamp)
{
fprintf(stderr, "timestamp: received %lu last %lu\n", timestamp, c2n->multilogin_last_timestamp);
return 1;
}
f = fopen(c2n->acc->cert_file, "r");
if (!f)
{
fprintf(stderr, "Failed to open certificate file %s\n", c2n->acc->cert_file);
return 1;
}
x = PEM_read_X509(f, 0, 0, 0);
fclose(f);
if (!x)
return 1;
pubkey = X509_get_pubkey(x);
if (!pubkey)
goto multilogin_err1;
sigdatas[0] = c2n->acc->id;
sigdatalengths[0] = PICA_ID_SIZE;
sigdatas[1] = buf + 4;
sigdatalengths[1] = sizeof(uint64_t);
sigdatas[2] = buf + 4 + sizeof(uint64_t);
switch (buf[4 + sizeof(uint64_t)])
{
case PICA_PROTO_NEWNODE_IPV4:
sigdatalengths[2] = PICA_PROTO_NODELIST_ITEM_IPV4_SIZE;
break;
default: // IPv6 !!
goto multilogin_err2;
break;
}
sigdatas[3] = NULL;
sigdatalengths[3] = 0;
siglen = payload_len - sizeof(uint64_t) - sigdatalengths[2];
if (siglen <= 0)
goto multilogin_err2;
sig = buf + 4 + sizeof(uint64_t) + sigdatalengths[2];
ret = PICA_signverify(pubkey, sigdatas, sigdatalengths, sig, siglen);
if (ret != 1)
{
fprintf(stderr, "signature verification failed\n");
goto multilogin_err2;
}
{
char ipaddr_string[INET6_ADDRSTRLEN];
unsigned char *ipv4_pos = buf + 4 + sizeof(uint64_t) + 1;
unsigned char *port_pos = buf + 4 + sizeof(uint64_t) + 1 + 4;
callbacks.multilogin_cb(timestamp, sigdatas[2],
inet_ntop(AF_INET, ipv4_pos, ipaddr_string, INET6_ADDRSTRLEN),
ntohs(*(uint16_t*)(port_pos)));
}
c2n->multilogin_last_timestamp = timestamp;
multilogin_err2:
EVP_PKEY_free(pubkey);
multilogin_err1:
X509_free(x);
return 1;
}
static unsigned int procmsg_CALLREQ(unsigned char* buf, unsigned int nb, void *p)
{
struct PICA_c2c *chan = (struct PICA_c2c *)p;
if (chan->call_state != PICA_CALL_STATE_IDLE)
// busy, send reject
{
struct PICA_proto_msg *mp;
mp = c2c_writebuf_push( chan, PICA_PROTO_CALLREJ, PICA_PROTO_CALLREJ_SIZE);
if (mp)
{
RAND_pseudo_bytes( mp->tail, 2);
}
else
return 0;
return 1;
}
chan->call_state = PICA_CALL_STATE_INCOMINGCALL;
callbacks.incoming_call_cb(chan->peer_id);
return 1;
}
static unsigned int procmsg_CALLANS(unsigned char* buf, unsigned int nb, void *p)
{
struct PICA_c2c *chan = (struct PICA_c2c *)p;
if (chan->call_state != PICA_CALL_STATE_CALLING)
{
fprintf(stderr, "unexpected CALLANS, we did not initiate a call");
return 0;
}
chan->call_state = PICA_CALL_STATE_ACTIVE;
callbacks.call_picked_up_cb(chan->peer_id);
return 1;
}
static unsigned int procmsg_CALLREJ(unsigned char* buf, unsigned int nb, void *p)
{
struct PICA_c2c *chan = (struct PICA_c2c *)p;
if (chan->call_state != PICA_CALL_STATE_CALLING)
{
fprintf(stderr, "unexpected CALLREJ, we did not initiate a call");
return 0;
}
chan->call_state = PICA_CALL_STATE_IDLE;
callbacks.call_rejected_cb(chan->peer_id);
return 1;
}
static unsigned int procmsg_CALLFIN(unsigned char* buf, unsigned int nb, void *p)
{
struct PICA_c2c *chan = (struct PICA_c2c *)p;
if (chan->call_state == PICA_CALL_STATE_IDLE)
{
fprintf(stderr, "unexpected CALLFIN, no call in progress");
return 0;
}
chan->call_state = PICA_CALL_STATE_IDLE;
callbacks.call_hangup_cb(chan->peer_id);
return 1;
}
static unsigned int procmsg_CALL_AUDIOPARAM(unsigned char* buf, unsigned int nb, void *p)
{
struct PICA_c2c *chan = (struct PICA_c2c *)p;
char codecnamebuf[24];
uint16_t sample_rate;
if (chan->call_state != PICA_CALL_STATE_ACTIVE)
{
fprintf(stderr, "unexpected AUDIOPARAM, no call in progress");
return 0;
}
chan->call_audio_params_are_set = 1;
codecnamebuf[0] = '\0';
strncat(codecnamebuf, buf + 2, PICA_PROTO_CALL_CODECNAMESIZE);
sample_rate = *(uint16_t*)(buf + 2 + PICA_PROTO_CALL_CODECNAMESIZE);
callbacks.call_audio_params_cb(chan->peer_id, codecnamebuf, sample_rate);
return 1;
}
static unsigned int procmsg_CALL_VIDEOPARAM(unsigned char* buf, unsigned int nb, void *p)
{
struct PICA_c2c *chan = (struct PICA_c2c *)p;
char codecnamebuf[24];
uint16_t width, height;
if (chan->call_state != PICA_CALL_STATE_ACTIVE)
{
fprintf(stderr, "unexpected VIDEOPARAM, no call in progress");
return 0;
}
chan->call_video_params_are_set = 1;
codecnamebuf[0] = '\0';
strncat(codecnamebuf, buf + 2, PICA_PROTO_CALL_CODECNAMESIZE);
width = *(uint16_t*)(buf + 2 + PICA_PROTO_CALL_CODECNAMESIZE);
height = *(uint16_t*)(buf + 2 + PICA_PROTO_CALL_CODECNAMESIZE + 2);
callbacks.call_video_params_cb(chan->peer_id, codecnamebuf, width, height);
return 1;
}
static unsigned int procmsg_CALL_AUDIO_PACKET(unsigned char *buf, unsigned int nb, void* p)
{
struct PICA_c2c *chan = (struct PICA_c2c *)p;
if (chan->call_state != PICA_CALL_STATE_ACTIVE)
{
fprintf(stderr, "unexpected CALL_AUDIO_PACKET, no call in progress");
return 0;
}
if (!chan->call_audio_params_are_set)
{
fprintf(stderr, "no AUDIOPARAM was set, can't process AUDIO_PACKET");
return 0;
}
if (*(uint16_t*)(buf + 2) != (nb - 4))
{
return 0;
}
uint16_t tail_size = *(uint16_t*)(buf + 2);
if (tail_size < PICA_PROTO_CALL_PACKET_HDRSIZE)
{
return 0;
}
uint16_t seq_num = *(uint16_t*)(buf + 4);
uint32_t timestamp = *(uint32_t*)(buf + 4 + PICA_PROTO_CALL_PACKET_SEQNUM_SIZE);
uint16_t pkt_size = tail_size - PICA_PROTO_CALL_PACKET_HDRSIZE;
callbacks.call_audio_packet_cb(chan->peer_id, seq_num, timestamp, pkt_size, buf + 4 + PICA_PROTO_CALL_PACKET_HDRSIZE);
return 1;
}
static unsigned int procmsg_CALL_VIDEO_PACKET(unsigned char *buf, unsigned int nb, void *p)
{
struct PICA_c2c *chan = (struct PICA_c2c *)p;
if (chan->call_state != PICA_CALL_STATE_ACTIVE)
{
fprintf(stderr, "unexpected CALL_VIDEO_PACKET, no call in progress");
return 0;
}
if (!chan->call_video_params_are_set)
{
fprintf(stderr, "no VIDEOPARAM was set, can't process VIDEO_PACKET");
return 0;
}
if (*(uint16_t*)(buf + 2) != (nb - 4))
{
return 0;
}
uint16_t tail_size = *(uint16_t*)(buf + 2);
if (tail_size < PICA_PROTO_CALL_PACKET_HDRSIZE)
{
return 0;
}
uint16_t seq_num = *(uint16_t*)(buf + 4);
uint32_t timestamp = *(uint32_t*)(buf + 4 + PICA_PROTO_CALL_PACKET_SEQNUM_SIZE);
uint16_t pkt_size = tail_size - PICA_PROTO_CALL_PACKET_HDRSIZE;
callbacks.call_video_packet_cb(chan->peer_id, seq_num, timestamp, pkt_size, buf + 4 + PICA_PROTO_CALL_PACKET_HDRSIZE);
return 1;
}
static int c2n_optstage7_multilogin(struct PICA_c2n *c2n)
{
struct PICA_proto_msg *mp;
void *sigdatas[4];
size_t sigdatalengths[4];
uint64_t timestamp;
unsigned char node_addr[PICA_PROTO_NODELIST_ITEM_IPV4_SIZE];
size_t siglen;
unsigned char *sig = NULL;
EVP_PKEY *pkey = NULL;
RSA *rsa = NULL;
FILE *f;
int ret = PICA_ERRNOMEM;
PICA_TRACEFUNC
sigdatas[0] = c2n->acc->id;
sigdatalengths[0] = PICA_ID_SIZE;
timestamp = (uint64_t) time(NULL);
sigdatas[1] = ×tamp;
sigdatalengths[1] = sizeof timestamp;
fprintf(stderr, "timestamp = %lu\n", timestamp);
//nodeaddr buf, IPv4 for now
// TODO IPv6, DNS
node_addr[0] = PICA_PROTO_NEWNODE_IPV4;
memcpy(node_addr + 1, &c2n->srv_addr.sin_addr.s_addr, 4);
memcpy(node_addr + 5, &c2n->srv_addr.sin_port, 2);
sigdatas[2] = node_addr;
sigdatalengths[2] = PICA_PROTO_NODELIST_ITEM_IPV4_SIZE;
sigdatas[3] = NULL;
sigdatalengths[3] = 0;
pkey = EVP_PKEY_new();
if (!pkey)
goto sendmultilogin_exit1;
f = fopen(c2n->acc->pkey_file, "r");
if (!f)
{
ret = PICA_ERRFILEOPEN;
goto sendmultilogin_exit2;
}
ret = PICA_ERRSSL;
rsa = PEM_read_RSAPrivateKey(f, NULL, c2n->acc->password_cb, c2n->acc->id);
fclose(f);
if (!rsa)
goto sendmultilogin_exit2;
ret = EVP_PKEY_set1_RSA(pkey, rsa);
if (ret != 1)
goto sendmultilogin_exit3;
ret = PICA_do_signature(pkey, sigdatas, sigdatalengths, &sig, &siglen);
if (ret != 1)
goto sendmultilogin_exit4;
if (siglen > PICA_PROTO_MULTILOGIN_MAXSIGSIZE)
{
ret = PICA_ERRMSGSIZE;
goto sendmultilogin_exit4;
}
mp = c2n_writebuf_push(c2n, PICA_PROTO_MULTILOGIN, 2 + 2 + sizeof timestamp + sigdatalengths[2] + siglen);
if (mp)
{
*((uint16_t*)mp->tail) = sizeof timestamp + sigdatalengths[2] + siglen;
memcpy(mp->tail + 2, ×tamp, sizeof timestamp);
fprintf(stderr, "timestamp = %lu\n", timestamp);
memcpy(mp->tail + 2 + sizeof timestamp, sigdatas[2], sigdatalengths[2]);
memcpy(mp->tail + 2 + sizeof timestamp + sigdatalengths[2], sig, siglen);
ret = PICA_OK;
}
else
ret = PICA_ERRNOMEM;
sendmultilogin_exit4:
free(sig);
sendmultilogin_exit3:
RSA_free(rsa);
sendmultilogin_exit2:
EVP_PKEY_free(pkey);
sendmultilogin_exit1:
return ret;
}
static int c2n_stage6_nodelistrequest(struct PICA_c2n *c2n)
{
PICA_TRACEFUNC
struct PICA_proto_msg *mp;
mp = c2n_writebuf_push(c2n, PICA_PROTO_CLNODELISTREQ, PICA_PROTO_CLNODELISTREQ_SIZE);
if (mp)
{
RAND_pseudo_bytes(mp->tail, 2);
}
else
return PICA_ERRNOMEM;
c2n->state = PICA_C2N_STATE_CONNECTED;
callbacks.c2n_established_cb(c2n);
if (c2n->multilogin_policy != PICA_MULTILOGIN_PROHIBIT)
return c2n_optstage7_multilogin(c2n);
return PICA_OK;
}
static int directc2c_stage2_starttls(struct PICA_directc2c *d, struct PICA_c2c *c2c)
{
PICA_TRACEFUNC
int ret;
d->ssl = SSL_new(c2c->acc->ctx);
SSL_set_fd(d->ssl, d->sck);
SSL_set_verify(d->ssl, SSL_VERIFY_PEER, verify_callback);
SSL_set_verify_depth(d->ssl, 1);
ret = SSL_connect(d->ssl);
if (ret == 0)
return PICA_ERRSSL;
if (ret < 0)
{
ret = SSL_get_error(d->ssl, ret);
if (ret != SSL_ERROR_WANT_READ && ret != SSL_ERROR_WANT_WRITE)
return PICA_ERRSSL;
}
d->state = PICA_DIRECTC2C_CONNSTATE_WAITINGTLS;
return PICA_OK;
}
int PICA_new_c2n(const struct PICA_acc *acc, const char *nodeaddr, unsigned int port,
enum PICA_directc2c_config direct_c2c_mode, int multilogin,
struct PICA_listener *l, struct PICA_c2n **ci)
{
PICA_TRACEFUNC
int ret, ret_err;
struct PICA_c2n *cid;
struct sockaddr_in a;
if (!(nodeaddr && acc && port && ci))
return PICA_ERRINVARG;
cid = *ci = (struct PICA_c2n*)calloc(sizeof(struct PICA_c2n), 1); //(1)
if (!cid)
return PICA_ERRNOMEM;
cid->acc = acc;
cid->directc2c_config = direct_c2c_mode;
cid->multilogin_policy = multilogin;
if (direct_c2c_mode == PICA_DIRECTC2C_CFG_ALLOWINCOMING)
{
cid->directc2c_listener = l;
}
cid->read_buf = calloc(PICA_CONNREADBUFSIZE, 1);//(2)
if (!cid->read_buf)
{
ret_err = PICA_ERRNOMEM;
goto error_ret_1;
}
cid->ssl_comm = SSL_new(acc->anon_ctx); //(3)
if (!cid->ssl_comm)
{
ret_err = PICA_ERRSSL;
goto error_ret_2;
}
//resolving name
{
struct addrinfo h, *r;
memset(&h, 0, sizeof(struct addrinfo));
h.ai_flags = 0;
#ifdef AI_IDN
h.ai_flags = h.ai_flags | AI_IDN;
#endif
#ifdef AI_ADDRCONFIG
h.ai_flags = h.ai_flags | AI_ADDRCONFIG;
#endif
h.ai_family = AF_INET; //CONF
h.ai_socktype = SOCK_STREAM;
h.ai_protocol = IPPROTO_TCP;
if (0 != getaddrinfo(nodeaddr, NULL, &h, &r))
{
ret_err = PICA_ERRDNSRESOLVE;
goto error_ret_4;
}
memset(&a, 0, sizeof(a));
memcpy(&a, r->ai_addr, r->ai_addrlen);
a.sin_family = AF_INET;
//a.sin_addr.s_addr = inet_addr (nodeaddr);
a.sin_port = htons(port);
freeaddrinfo(r);
}
cid->srv_addr = a;
cid->sck_comm = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); //(5)
if (cid->sck_comm == SOCKET_ERROR)
{
ret_err = PICA_ERRSOCK;
goto error_ret_3;
}
IOCTLSETNONBLOCKINGSOCKET(cid->sck_comm, 1);
cid->state = PICA_C2N_STATE_CONNECTING;
ret = connect(cid->sck_comm, (struct sockaddr*)&cid->srv_addr, sizeof(cid->srv_addr));
if (ret == 0)
return c2n_stage2_startanontls(cid);
if ((ret_err = process_first_async_connect_result(ret)) != PICA_OK)
goto error_ret_4;
return PICA_OK;
error_ret_4: //(4)
SHUTDOWN(cid->sck_comm);
CLOSE(cid->sck_comm);
error_ret_3: //(3)
SSL_free(cid->ssl_comm);
error_ret_2: //(2)
free(cid->read_buf);
error_ret_1: //(1)
free(cid);
*ci = 0;
return ret_err;
}
int PICA_new_c2c(struct PICA_c2n *ci, const unsigned char *peer_id, struct PICA_listener *l, struct PICA_c2c **chn)
{
PICA_TRACEFUNC
struct PICA_proto_msg *mp;
struct PICA_c2c *chnl;
if (!(ci && chn))
return PICA_ERRINVARG;
if (ci->state != PICA_C2N_STATE_CONNECTED)
return PICA_ERRINVARG;
if (!c2n_alloc_c2c(ci, chn, peer_id, PICA_C2C_OUTGOING))
return PICA_ERRNOMEM;
chnl = *chn;
mp = c2n_writebuf_push( ci, PICA_PROTO_CONNREQOUTG, PICA_PROTO_CONNREQOUTG_SIZE);
if (mp)
{
memcpy(mp->tail, peer_id, PICA_ID_SIZE);
}
else
return PICA_ERRNOMEM;
return PICA_OK;
}
static int process_async_ssl_errors(SSL *ssl, int ret, int *want_write, int *want_read)
{
PICA_TRACEFUNC
if (ret == 0)
return PICA_ERRDISCONNECT;
if (ret < 0)
switch(SSL_get_error(ssl, ret))
{
case SSL_ERROR_WANT_WRITE:
if (want_write)
*want_write = 1;
break;
case SSL_ERROR_WANT_READ:
if (want_read)
*want_read = 1;
break;
default:
return PICA_ERRSSL;
};
return PICA_OK;
}
static int process_first_async_connect_result(int connect_ret)
{
PICA_TRACEFUNC
if (connect_ret == SOCKET_ERROR)
{
#ifndef WIN32
if (errno == EINPROGRESS || errno == EINTR)
return PICA_OK;
#else
if (WSAGetLastError() == WSAEWOULDBLOCK)
return PICA_OK;
#endif
return PICA_ERRSOCK;
}
return PICA_OK;
}
static int check_async_connect_result(int sock, struct sockaddr* a, int addrlen)
{
PICA_TRACEFUNC
int ret = PICA_ERRSOCK;
#ifndef WIN32
int optval;
socklen_t optlen = sizeof(optval);
if (0 == getsockopt(sock, SOL_SOCKET, SO_ERROR, &optval, &optlen) && optval == 0)
#else
if (SOCKET_ERROR == connect(sock, a, addrlen) &&
WSAEISCONN == WSAGetLastError())
#endif
ret = PICA_OK;
return ret;
}
static int process_c2n(struct PICA_c2n *c2n, fd_set *rfds, fd_set *wfds)
{
int ret = PICA_OK;
//puts("process_c2n()");//debug
switch(c2n->state)
{
case PICA_C2N_STATE_NEW:
break;
case PICA_C2N_STATE_CONNECTING:
if (FD_ISSET(c2n->sck_comm, wfds))
{
ret = check_async_connect_result(c2n->sck_comm, (struct sockaddr*)&c2n->srv_addr, sizeof(c2n->srv_addr));
if (ret == PICA_OK)
{
ret = c2n_stage2_startanontls(c2n);
}
}
break;
case PICA_C2N_STATE_WAITINGANONTLS:
if (FD_ISSET(c2n->sck_comm, wfds) || FD_ISSET(c2n->sck_comm, rfds))
{
int sslret;
sslret = SSL_connect(c2n->ssl_comm);
if (sslret == 1)
ret = c2n_stage3_sendreq(c2n);
else
{
ret = process_async_ssl_errors(c2n->ssl_comm, sslret, &c2n->want_write, 0);
}
}
break;
case PICA_C2N_STATE_WAITINGREP:
if (FD_ISSET(c2n->sck_comm, wfds))
{
ret = PICA_write_c2n(c2n);
}
if (FD_ISSET(c2n->sck_comm, rfds))
{
ret = PICA_read_c2n(c2n);
}
if (ret == PICA_OK && c2n->init_resp_ok != 0)
{
if (c2n->init_resp_ok == 1)
ret = c2n_stage4_shutdownanontls(c2n);
else
{
if (c2n->node_ver_major > PICA_PROTO_VER_HIGH ||
(c2n->node_ver_major == PICA_PROTO_VER_HIGH && c2n->node_ver_minor > PICA_PROTO_VER_LOW))
ret = PICA_ERRPROTONEW;
else
ret = PICA_ERRPROTOOLD;
}
}
break;
case PICA_C2N_STATE_WAITINGANONTLSSHUTDOWN:
if (FD_ISSET(c2n->sck_comm, wfds) || FD_ISSET(c2n->sck_comm, rfds))
{
int sslret;
sslret = SSL_shutdown(c2n->ssl_comm);
if (sslret == 0)
break;
if (sslret < 0)
ret = process_async_ssl_errors(c2n->ssl_comm, sslret, &c2n->want_write, 0);
if (sslret == 1)
ret = c2n_stage5_starttls(c2n);
}
break;
case PICA_C2N_STATE_WAITINGTLS:
if (FD_ISSET(c2n->sck_comm, wfds) || FD_ISSET(c2n->sck_comm, rfds))
{
int sslret;
sslret = SSL_accept(c2n->ssl_comm);
if (sslret == 1)
ret = c2n_stage6_nodelistrequest(c2n);
else if (sslret == 0)
ret = PICA_ERRSSL;
else if (sslret < 0)
ret = process_async_ssl_errors(c2n->ssl_comm, sslret, &c2n->want_write, 0);
}
break;
case PICA_C2N_STATE_CONNECTED:
if (FD_ISSET(c2n->sck_comm, wfds))
ret = PICA_write_c2n(c2n);
if (FD_ISSET(c2n->sck_comm, rfds))
ret = PICA_read_c2n(c2n);
break;
}
return ret;
}
static int process_c2c(struct PICA_c2c *c2c, fd_set *rfds, fd_set *wfds)
{
int ret = PICA_OK;
switch(c2c->state)
{
case PICA_C2C_STATE_NEW:
break;
case PICA_C2C_STATE_CONNECTING:
if (FD_ISSET(c2c->sck_data, wfds))
{
ret = check_async_connect_result(c2c->sck_data, (struct sockaddr*)&c2c->conn->srv_addr, sizeof(c2c->conn->srv_addr));
if (ret == PICA_OK)
{
ret = c2c_stage2_startanontls(c2c);
}
}
break;
case PICA_C2C_STATE_WAITINGANONTLS:
if (FD_ISSET(c2c->sck_data, wfds) || FD_ISSET(c2c->sck_data, rfds))
{
int sslret;
sslret = SSL_connect(c2c->ssl);
if (sslret == 1)
ret = c2c_stage3_connid(c2c);
else
{
ret = process_async_ssl_errors(c2c->ssl, sslret, &c2c->want_write, NULL);
}
}
break;
case PICA_C2C_STATE_CONNID:
if (FD_ISSET(c2c->sck_data, wfds))
{
ret = PICA_write_c2c(c2c);
}
if (c2c->write_pos == 0)
{
ret = c2c_stage4_shutdownanontls(c2c);
}
break;
case PICA_C2C_STATE_WAITINGANONTLSSHUTDOWN:
if (FD_ISSET(c2c->sck_data, wfds) || FD_ISSET(c2c->sck_data, rfds))
{
int sslret;
sslret = SSL_shutdown(c2c->ssl);
if (sslret == 0)
break;
if (sslret < 0)
ret = process_async_ssl_errors(c2c->ssl, sslret, &c2c->want_write, NULL);
if (sslret == 1)
ret = c2c_stage5_starttls(c2c);
}
break;
case PICA_C2C_STATE_WAITINGTLS:
if (FD_ISSET(c2c->sck_data, wfds) || FD_ISSET(c2c->sck_data, rfds))
{
int sslret;
if (c2c->outgoing)
sslret = SSL_connect(c2c->ssl);
else
sslret = SSL_accept(c2c->ssl);
if (sslret < 0)
{
sslret = SSL_get_error(c2c->ssl, sslret);
if (sslret != SSL_ERROR_WANT_READ && sslret != SSL_ERROR_WANT_WRITE)
ret = PICA_ERRSSL;
if (sslret == SSL_ERROR_WANT_WRITE)
c2c->want_write = 1;
}
else if(sslret == 0)
{
ret = PICA_ERRSSL;
}
else if (sslret == 1)
{
ret = c2c_verify_peer_cert(c2c);
if (ret == PICA_OK && c2c->outgoing)
ret = c2c_stage6_sendc2cconnreq(c2c);
if (ret == PICA_OK && c2c->outgoing == 0)
c2c->state = PICA_C2C_STATE_WAITINGC2CPROTOVER;
}
}
break;
case PICA_C2C_STATE_WAITINGREP:
case PICA_C2C_STATE_WAITINGC2CPROTOVER:
if (FD_ISSET(c2c->sck_data, wfds))
{
ret = PICA_write_c2c(c2c);
}
if (ret == PICA_OK && FD_ISSET(c2c->sck_data, rfds))
{
ret = PICA_read_c2c(c2c);
}
break;
case PICA_C2C_STATE_ACTIVE:
if (FD_ISSET(c2c->sck_data, wfds)
|| c2c->directc2c_state == PICA_DIRECTC2C_STATE_ACTIVE && FD_ISSET(c2c->direct->sck, wfds))
{
if (c2c->write_pos > 0)
{
ret = PICA_write_c2c(c2c);
}
else if (c2c->sendfilestate == PICA_CHANSENDFILESTATE_SENDING)
{
ret = PICA_send_file_fragment(c2c);
}
}
if (ret == PICA_OK && (FD_ISSET(c2c->sck_data, rfds)
|| c2c->directc2c_state == PICA_DIRECTC2C_STATE_ACTIVE && FD_ISSET(c2c->direct->sck, rfds)))
{
ret = PICA_read_c2c(c2c);
}
break;
}
if (c2c->state != PICA_C2C_STATE_ACTIVE && (time(0) - c2c->timestamp) > PICA_C2C_ACTIVATE_TIMEOUT)
{
ret = PICA_ERRTIMEDOUT;
}
if (ret != PICA_OK)
{
if (c2c->state > PICA_C2C_STATE_NEW && c2c->state < PICA_C2C_STATE_ACTIVE)
callbacks.c2c_failed_cb(c2c->peer_id);
else
callbacks.c2c_closed_cb(c2c->peer_id, ret);
}
return ret;
}
static struct PICA_c2c * find_matching_c2c(struct PICA_c2n *c2n, struct PICA_directc2c *d)
{
PICA_TRACEFUNC
struct PICA_c2c *c2c;
unsigned char idbuf[PICA_ID_SIZE];
if (!PICA_id_from_X509(d->peer_cert, idbuf))
return NULL;
c2c = c2n->chan_list_head;
while(c2c)
{
if (c2c->directc2c_state == PICA_DIRECTC2C_STATE_WAITINGINCOMING)
{
if (memcmp(idbuf, c2c->peer_id, PICA_ID_SIZE) == 0)
{
return c2c;
}
}
c2c = c2c->next;
}
return NULL;
}
static int directc2c_verify_peer_cert(struct PICA_directc2c *d, struct PICA_c2c *c2c)
{
PICA_TRACEFUNC
return verify_peer_cert_common(&d->peer_cert, d->ssl, c2c->peer_id);
}
static void process_directc2c(struct PICA_c2n *c2n, fd_set *rfds, fd_set *wfds)
{
struct PICA_directc2c *d;
struct PICA_c2c *c2c = NULL;
int ret;
//process accepted connections
if (c2n->directc2c_listener)
{
struct PICA_directc2c **pdprev;
d = c2n->directc2c_listener->accepted_connections;
pdprev = &c2n->directc2c_listener->accepted_connections;
while(d)
{
if (d->state == PICA_DIRECTC2C_CONNSTATE_ACTIVE)
{
if ((c2c = find_matching_c2c(c2n, d)) && (verify_peer_cert_common(&d->peer_cert, d->ssl, c2c->peer_id) == PICA_OK))
{
if (c2c->direct)
PICA_close_directc2c(c2c->direct);
c2c->direct = d;
ret = PICA_send_directc2c_switch(c2c);//??? who should send first?
if (ret != PICA_OK)
{
PICA_close_directc2c(d);
c2c->directc2c_state = PICA_DIRECTC2C_STATE_INACTIVE;
}
else
{
callbacks.direct_c2c_established_cb(c2c->peer_id, SSL_get_cipher_name(d->ssl));
c2c->directc2c_state = PICA_DIRECTC2C_STATE_ACTIVE;
}
//remove from listener's list
*pdprev = d->next;
d->next = 0;
}
else
{
//close and remove from listener's list
*pdprev = d->next;
PICA_close_directc2c(d);
}
}
pdprev = &d->next;
d = d->next;
//-- killptr?
}
}
//process outgoing connections
c2c = c2n->chan_list_head;
while(c2c)
{
if (c2c->directc2c_state == PICA_DIRECTC2C_STATE_FAILEDTOCONNECT)
{
if (c2c->outgoing == PICA_C2C_OUTGOING && c2c->conn->directc2c_config == PICA_DIRECTC2C_CFG_ALLOWINCOMING)
{
ret = PICA_send_directc2cfailed(c2c);
if (ret == PICA_OK)
c2c->directc2c_state = PICA_DIRECTC2C_STATE_WAITINGINCOMING;
else
c2c->directc2c_state = PICA_DIRECTC2C_STATE_INACTIVE;
}
else
c2c->directc2c_state = PICA_DIRECTC2C_STATE_INACTIVE;
}
if (c2c->directc2c_state == PICA_DIRECTC2C_STATE_CONNECTING)
{
d = c2c->direct;
ret = PICA_OK;
switch(d->state)
{
case PICA_DIRECTC2C_CONNSTATE_CONNECTING:
if (FD_ISSET(d->sck, wfds))
{
ret = check_async_connect_result(d->sck, (struct sockaddr*)&d->addr, sizeof(d->addr));
if (ret == PICA_OK)
{
ret = directc2c_stage2_starttls(d, c2c);
}
else
{
d->state = PICA_DIRECTC2C_CONNSTATE_FAILED;
}
}
break;
case PICA_DIRECTC2C_CONNSTATE_WAITINGTLS:
if (FD_ISSET(d->sck, wfds) || FD_ISSET(d->sck, rfds))
{
ret = SSL_connect(d->ssl);
if (ret != 1)
{
ret = process_async_ssl_errors(d->ssl, ret, &d->want_write, 0);
if (ret != PICA_OK)
{
d->state = PICA_DIRECTC2C_CONNSTATE_FAILED;
}
break;
}
ret = directc2c_verify_peer_cert(d, c2c);
if (ret != PICA_OK)
{
d->state = PICA_DIRECTC2C_CONNSTATE_FAILED;
break;
}
ret = PICA_send_directc2c_switch(c2c);
if (ret != PICA_OK)
{
d->state = PICA_DIRECTC2C_CONNSTATE_FAILED;
break;
}
d->state = PICA_DIRECTC2C_CONNSTATE_ACTIVE;
}
break;
case PICA_DIRECTC2C_CONNSTATE_FAILED:
ret = directc2c_connect_next(d, c2c);
if (ret == PICA_ERRNOTFOUND)
{
PICA_close_directc2c(d);
c2c->direct = 0;
c2c->directc2c_state = PICA_DIRECTC2C_STATE_FAILEDTOCONNECT;
}
break;
case PICA_DIRECTC2C_CONNSTATE_ACTIVE:
c2c->directc2c_state = PICA_DIRECTC2C_STATE_ACTIVE;
callbacks.direct_c2c_established_cb(c2c->peer_id, SSL_get_cipher_name(d->ssl));
break;
}
// if (ret != PICA_OK)
// {
// PICA_close_directc2c(d);
// c2c->direct = 0;
// if (c2c->directc2c_state != PICA_DIRECTC2C_STATE_FAILEDTOCONNECT)
// c2c->directc2c_state = PICA_DIRECTC2C_STATE_INACTIVE;//fallback to c2c through node?
// }
}
c2c = c2c->next;
}
}
static void listener_add_connection(struct PICA_listener *lst, SOCKET s)
{
PICA_TRACEFUNC
struct PICA_directc2c *nc;
int ret;
nc = calloc(1, sizeof(struct PICA_directc2c));
nc->is_outgoing = PICA_DIRECTC2C_INCOMING;
nc->sck = s;
nc->ssl = SSL_new(lst->acc->ctx);
nc->state = PICA_DIRECTC2C_CONNSTATE_NEW;
SSL_set_fd(nc->ssl, nc->sck);
SSL_set_verify(nc->ssl, SSL_VERIFY_PEER, verify_callback);
SSL_set_verify_depth(nc->ssl, 1);
ret = SSL_accept(nc->ssl);
if (ret == 0)
{
free(nc);
return;
}
if (ret < 0)
{
ret = SSL_get_error(nc->ssl, ret);
if (ret != SSL_ERROR_WANT_READ && ret != SSL_ERROR_WANT_WRITE)
{
free(nc);
return;
}
}
nc->state = PICA_DIRECTC2C_CONNSTATE_WAITINGTLS;
nc->next = lst->accepted_connections;
lst->accepted_connections = nc;
}
static int process_listener_conn(struct PICA_directc2c *conn, fd_set *rfds, fd_set *wfds)
{
if (conn->peer_cert == NULL)
{
if (FD_ISSET(conn->sck, rfds) || FD_ISSET(conn->sck, wfds))
{
int ret;
ret = SSL_accept(conn->ssl);
if (ret == 1)
{
conn->peer_cert = SSL_get_peer_certificate(conn->ssl);
if (!conn->peer_cert)
return PICA_ERRSSL;
conn->state = PICA_DIRECTC2C_CONNSTATE_ACTIVE;
return PICA_OK;
}
if (ret == 0)
return PICA_ERRSSL;
if (ret < 0)
{
ret = SSL_get_error(conn->ssl, ret);
if (ret == SSL_ERROR_WANT_WRITE)
conn->want_write = 1;
if (ret != SSL_ERROR_WANT_READ && ret != SSL_ERROR_WANT_WRITE)
return PICA_ERRSSL;
}
}
}
return PICA_OK;
}
static int process_listener(struct PICA_listener *lst, fd_set *rfds, fd_set *wfds)
{
struct PICA_directc2c *conn;
struct PICA_directc2c **pprevconn;
if (FD_ISSET(lst->sck_listener, rfds))
{
SOCKET s;
struct sockaddr_in addr;
int addrsize = sizeof(struct sockaddr_in);
s = accept(lst->sck_listener, (struct sockaddr*)&addr, &addrsize);
if (s >= 0)
{
IOCTLSETNONBLOCKINGSOCKET(s, 1);
listener_add_connection(lst, s);
}
}
conn = lst->accepted_connections;
pprevconn = &lst->accepted_connections;
while(conn)
{
int ret;
struct PICA_directc2c *kill_ptr = NULL;
if ((ret = process_listener_conn(conn, rfds, wfds)) != PICA_OK)
{
kill_ptr = conn;
}
if (!kill_ptr)
pprevconn = &conn->next;
conn = conn->next;
if (kill_ptr)
{
*pprevconn = kill_ptr->next;
PICA_close_directc2c(kill_ptr);
}
}
return PICA_OK;
}
static void fdset_add(fd_set *fds, int fd, int *max)
{
FD_SET(fd, fds);
if (fd > *max)
*max = fd;
}
int PICA_event_loop(struct PICA_c2n **connections, int timeout)
{
fd_set rfds, wfds;
struct timeval tv;
int ret, nfds = 0;
struct PICA_c2n **ic2n;
//puts("PICA_event_loop");//debug
tv.tv_sec = timeout / 1000;
tv.tv_usec = (timeout % 1000) * 1000;
FD_ZERO(&rfds);
FD_ZERO(&wfds);
ic2n = connections;
while(ic2n && *ic2n)
{
struct PICA_c2c *ic2c;
struct PICA_directc2c *ilstcon;
//printf("event_loop: adding c2n %p\n", *ic2n);//debug
fdset_add(&rfds, (*ic2n)->sck_comm, &nfds);
if ((*ic2n)->write_pos || (*ic2n)->state == PICA_C2N_STATE_CONNECTING
|| (*ic2n)->want_write/*(*ic2n)->state == PICA_C2N_STATE_WAITINGTLS*/)
{
(*ic2n)->want_write = 0;
fdset_add(&wfds, (*ic2n)->sck_comm, &nfds);
}
if ((*ic2n)->directc2c_listener)
{
fdset_add(&rfds, (*ic2n)->directc2c_listener->sck_listener, &nfds);
ilstcon = (*ic2n)->directc2c_listener->accepted_connections;
while (ilstcon)
{
fdset_add(&rfds, ilstcon->sck, &nfds);
if (ilstcon->want_write)
fdset_add(&wfds, ilstcon->sck, &nfds);
ilstcon->want_write = 0;
ilstcon = ilstcon->next;
}
}
ic2c = (*ic2n)->chan_list_head;
while(ic2c)
{
if (ic2c->state >= PICA_C2C_STATE_CONNECTING)
{
fdset_add(&rfds, ic2c->sck_data, &nfds);
if ((ic2c->write_pos || ic2c->state == PICA_C2C_STATE_CONNECTING || (ic2c->state == PICA_C2C_STATE_WAITINGTLS && ic2c->want_write)
|| ic2c->sendfilestate == PICA_CHANSENDFILESTATE_SENDING) && ic2c->directc2c_state != PICA_DIRECTC2C_STATE_ACTIVE)
{
ic2c->want_write = 0;
fdset_add(&wfds, ic2c->sck_data, &nfds);
}
}
if (ic2c->state == PICA_C2C_STATE_ACTIVE && (ic2c->directc2c_state == PICA_DIRECTC2C_STATE_CONNECTING || ic2c->directc2c_state == PICA_DIRECTC2C_STATE_ACTIVE)
&& ic2c->direct && ic2c->direct->state >= PICA_DIRECTC2C_CONNSTATE_CONNECTING)
{
fdset_add(&rfds, ic2c->direct->sck, &nfds);
if (ic2c->write_pos || ic2c->direct->state == PICA_DIRECTC2C_CONNSTATE_CONNECTING || ic2c->direct->want_write || ic2c->sendfilestate == PICA_CHANSENDFILESTATE_SENDING)
{
ic2c->direct->want_write = 0;
fdset_add(&wfds, ic2c->direct->sck, &nfds);
}
}
ic2c = ic2c->next;
}
ic2n++;
}
ret = select(nfds + 1, &rfds, &wfds, NULL, &tv);
if (ret == -1)
{
#ifndef WIN32
if (errno == EINTR)
return PICA_OK;
else
return PICA_ERRSOCK;
#else
#warning "implement handling of select() errors on Windows!"
#endif
}
//printf("select() returned %i\n", ret);//debug
//processing listeners, c2n and c2c connections
ic2n = connections;
while(ic2n && *ic2n)
{
struct PICA_c2c *ic2c;
//processing current c2n
ret = process_c2n(*ic2n, &rfds, &wfds);
if (ret != PICA_OK)
{
if ((*ic2n)->state == PICA_C2N_STATE_CONNECTED)
callbacks.c2n_closed_cb(*ic2n, ret);
else
callbacks.c2n_failed_cb(*ic2n, ret);
PICA_close_c2n(*ic2n);
}
else
{
struct PICA_c2c *kill_ptr = 0;
//processing listener associated with current c2n
if ((*ic2n)->directc2c_listener)
{
ret = process_listener((*ic2n)->directc2c_listener, &rfds, &wfds);
if (ret != PICA_OK)
{
callbacks.listener_error_cb((*ic2n)->directc2c_listener, ret);
PICA_close_listener((*ic2n)->directc2c_listener);
}
}
ic2c = (*ic2n)->chan_list_head;
//processing c2c connections associated with current c2n
while(ic2c)
{
ret = process_c2c(ic2c, &rfds, &wfds);
if (ret != PICA_OK)
kill_ptr = ic2c;
ic2c = ic2c->next;
if (kill_ptr)
{
PICA_close_c2c(kill_ptr);
kill_ptr = 0;
}
}
//processing direct c2c connections associated with current c2n
if ((*ic2n)->directc2c_config != PICA_DIRECTC2C_CFG_DISABLED)
{
process_directc2c(*ic2n, &rfds, &wfds);
}
}
ic2n++;
}
return PICA_OK;
}
int PICA_write_ssl(SSL *ssl, unsigned char *buf, unsigned int *ppos, unsigned int *btw)
{
int ret;
if (*ppos > 0 )
{
if (*btw == 0)
*btw = *ppos;
ret = SSL_write( ssl, buf, *btw);
if (ret > 0)
{
if (ret < *ppos)
memmove( buf, buf + ret, *ppos - ret);
*ppos -= ret;
*btw = 0;
}
if (ret < 0)
{
ret = SSL_get_error( ssl, ret);
if (! (ret == SSL_ERROR_WANT_WRITE || ret == SSL_ERROR_WANT_READ))
return PICA_ERRSSL;
}
if (ret == 0)
return PICA_ERRSERV;
}
return PICA_OK;
}
int PICA_write_c2n(struct PICA_c2n *ci)
{
int ret = PICA_ERRINVARG;
ret = PICA_write_ssl( ci->ssl_comm, ci->write_buf, &ci->write_pos, &ci->write_sslbytestowrite);
if (ci->disconnect_on_empty_write_buf)
PICA_close_c2n(ci);
return ret;
}
int PICA_write_c2c(struct PICA_c2c *chn)
{
int ret = PICA_ERRINVARG;
if (chn->state != PICA_C2C_STATE_ACTIVE && chn->state != PICA_C2C_STATE_CONNID && chn->state != PICA_C2C_STATE_WAITINGREP)
{
fputs("PICA_write_c2c() was called for incorrect state!\n", stderr);
exit(-1);
}
{
SSL *ssl_to_write = chn->ssl;
if (chn->switched_to_directc2c_write)
ssl_to_write = chn->direct->ssl;
if (chn->directc2c_write_barrier_pos)
{
if (chn->directc2c_write_before_barrier_pos == 0)
chn->directc2c_write_before_barrier_pos = chn->directc2c_write_barrier_pos;
ret = PICA_write_ssl(chn->ssl, chn->write_buf, &chn->directc2c_write_before_barrier_pos, &chn->write_sslbytestowrite);
if (ret == PICA_OK && chn->directc2c_write_before_barrier_pos == 0)
{
chn->switched_to_directc2c_write = 1;
memmove(chn->write_buf, chn->write_buf + chn->directc2c_write_barrier_pos, chn->write_pos - chn->directc2c_write_barrier_pos);
chn->write_pos -= chn->directc2c_write_barrier_pos;
chn->directc2c_write_barrier_pos = 0;
}
}
else
ret = PICA_write_ssl(ssl_to_write, chn->write_buf, &chn->write_pos, &chn->write_sslbytestowrite);
}
if (chn->disconnect_on_empty_write_buf && chn->write_pos == 0)
PICA_close_c2c(chn);
return ret;
}
int PICA_read_ssl(SSL *ssl, unsigned char *buf, unsigned int *ppos, unsigned int size)
{
int ret;
ret = SSL_read(ssl, buf + *ppos, size - *ppos);
if (!ret)
return PICA_ERRSSL;
if (ret < 0)
switch(SSL_get_error(ssl, ret))
{
case SSL_ERROR_WANT_WRITE:
case SSL_ERROR_WANT_READ:
break;
default:
return PICA_ERRSSL;
};
if (ret > 0)
{
*ppos += ret;
}
return PICA_OK;
}
int PICA_read_c2c(struct PICA_c2c *chn)
{
int ret;
SSL *ssl_to_read = chn->ssl;
if (chn->state != PICA_C2C_STATE_WAITINGREP && chn->state != PICA_C2C_STATE_WAITINGC2CPROTOVER && chn->state != PICA_C2C_STATE_ACTIVE)
{
fputs("PICA_read_c2c() was called for incorrect state!\n", stderr);
exit(-1);
}
if (chn->switched_to_directc2c_read)
ssl_to_read = chn->direct->ssl;
ret = PICA_read_ssl(ssl_to_read, chn->read_buf, &chn->read_pos, PICA_CHANREADBUFSIZE);
if (ret == PICA_OK)
{
const struct PICA_msginfo *msgs;
unsigned int nmsgs;
nmsgs = MSGINFO_MSGSNUM(c2c_messages);
msgs = c2c_messages;
if(!PICA_processdatastream(chn->read_buf, &(chn->read_pos ), chn, msgs, nmsgs))
return PICA_ERRSERV;
}
return ret;
}
int PICA_read_c2n(struct PICA_c2n *ci)
{
int ret;
ret = PICA_read_ssl(ci->ssl_comm, ci->read_buf, &ci->read_pos, PICA_CONNREADBUFSIZE);
if (ret == PICA_OK)
{
if(!PICA_processdatastream( ci->read_buf, &(ci->read_pos), ci, c2n_messages, MSGINFO_MSGSNUM(c2n_messages)))
return PICA_ERRSERV;
}
return ret;
}
struct PICA_proto_msg* c2n_writebuf_push(struct PICA_c2n *ci, unsigned int msgid, unsigned int size)
{
struct PICA_proto_msg *mp;
if (! ci->write_buf)
{
ci->write_buf = calloc(PICA_CONNWRITEBUFSIZE, 1);
if (!ci->write_buf)
return 0;
ci->write_buflen = PICA_CONNWRITEBUFSIZE;
}
if (ci->write_buflen - ci->write_pos < size)
{
unsigned char*p;
p = realloc(ci->write_buf, size + ci->write_pos);
if (p)
{
ci->write_buf = p;
ci->write_buflen = size + ci->write_pos;
}
else
return 0;
}
mp = (struct PICA_proto_msg *)(ci->write_buf + ci->write_pos);
mp->head[0] = mp->head[1] = msgid;
ci->write_pos += size;
return mp;
}
struct PICA_proto_msg* c2c_writebuf_push(struct PICA_c2c *chn, unsigned int msgid, unsigned int size)
{
struct PICA_proto_msg *mp;
if (! chn->write_buf)
{
chn->write_buf = calloc(PICA_CHANWRITEBUFSIZE, 1);
if (!chn->write_buf)
return 0;
chn->write_buflen = PICA_CHANWRITEBUFSIZE;
}
if (chn->write_buflen - chn->write_pos < size)
{
unsigned char*p;
p = realloc(chn->write_buf, size + chn->write_pos);
if (p)
{
chn->write_buf = p;
chn->write_buflen = size + chn->write_pos;
}
else
return 0;
}
mp = (struct PICA_proto_msg *)(chn->write_buf + chn->write_pos);
mp->head[0] = mp->head[1] = msgid;
chn->write_pos += size;
return mp;
}
int PICA_send_directc2caddrlist(struct PICA_c2c *chn)
{
PICA_TRACEFUNC
if (chn->conn->directc2c_config == PICA_DIRECTC2C_CFG_ALLOWINCOMING && chn->conn->directc2c_listener)
{
struct PICA_proto_msg *mp;
//TODO send all possible interface addresses, not one
unsigned int len = PICA_PROTO_DIRECTC2C_ADDRLIST_ITEM_IPV4_SIZE;
if ((mp = c2c_writebuf_push(chn, PICA_PROTO_DIRECTC2C_ADDRLIST, len + 4)))
{
*((uint16_t*)mp->tail) = len;
*((uint8_t*)mp->tail + 2) = PICA_PROTO_DIRECTC2C_IPV4;
*((uint32_t*)(mp->tail + 3)) = chn->conn->directc2c_listener->public_addr_ipv4;
*((uint16_t*)(mp->tail + 7)) = htons(chn->conn->directc2c_listener->public_port);
chn->directc2c_state = PICA_DIRECTC2C_STATE_WAITINGINCOMING;
}
}
return PICA_OK;
}
static int PICA_send_directc2cfailed(struct PICA_c2c *chn)
{
PICA_TRACEFUNC
struct PICA_proto_msg *mp;
if ((mp = c2c_writebuf_push(chn, PICA_PROTO_DIRECTC2C_FAILED, PICA_PROTO_DIRECTC2C_FAILED_SIZE)))
{
RAND_pseudo_bytes(mp->tail, 2);
}
else
return PICA_ERRNOMEM;
return PICA_OK;
}
static int PICA_send_directc2c_switch(struct PICA_c2c *chn)
{
PICA_TRACEFUNC
struct PICA_proto_msg *mp;
if ((mp = c2c_writebuf_push(chn, PICA_PROTO_DIRECTC2C_SWITCH, PICA_PROTO_DIRECTC2C_SWITCH_SIZE)))
{
RAND_pseudo_bytes(mp->tail, 2);
}
else
return PICA_ERRNOMEM;
chn->directc2c_write_barrier_pos = chn->write_pos;
return PICA_OK;
}
int PICA_send_msg(struct PICA_c2c *chn, char *buf, unsigned int len)
{
struct PICA_proto_msg *mp;
if (len > PICA_PROTO_C2CMSG_MAXDATASIZE)
return PICA_ERRMSGSIZE;
if ((mp = c2c_writebuf_push(chn, PICA_PROTO_MSGUTF8, len + 4)))
{
*((uint16_t*)mp->tail) = len;
memcpy(mp->tail + 2, buf, len);
}
else
return PICA_ERRNOMEM;
return PICA_OK;
}
int PICA_send_file(struct PICA_c2c *chn, const char *filepath)
{
struct PICA_proto_msg *mp;
size_t namelen;
uint64_t file_size;
int ret;
const char *filename;//file name part of the filepath
if (chn->sendfilestate != PICA_CHANSENDFILESTATE_IDLE)
return PICA_ERRFILETRANSFERINPROGRESS;
if (filepath == NULL)
return PICA_ERRINVFILENAME;
#ifndef WIN32
filename = strrchr(filepath, '/');
#else
filename = strrchr(filepath, '\\');
#endif
if (filename)
filename++;
else
filename = filepath;
namelen = strlen(filename);
if (namelen == 0 || namelen > PICA_PROTO_C2CMSG_MAXFILENAMESIZE)
return PICA_ERRINVFILENAME;
if ((ret = PICA_sendfile_open_read(chn, filepath, &file_size)) != PICA_OK)
return ret;
if ((mp = c2c_writebuf_push(chn, PICA_PROTO_SENDFILEREQUEST, namelen + 12)))
{
*((uint16_t*)mp->tail) = namelen + 8;
*((uint64_t*)(mp->tail + 2)) = file_size;
memcpy(mp->tail + 10, filename, namelen);
}
else
return PICA_ERRNOMEM;
chn->sendfilestate = PICA_CHANSENDFILESTATE_SENTREQ;
chn->sendfile_size = file_size;
chn->sendfile_pos = 0;
return PICA_OK;
}
int PICA_pause_file(struct PICA_c2c *chan, int sending)
{
int senderctl = PICA_PROTO_FILECONTROL_VOID;
int receiverctl = PICA_PROTO_FILECONTROL_VOID;
if (sending)
{
if (chan->sendfilestate != PICA_CHANSENDFILESTATE_SENDING)
return PICA_ERRINVARG;
chan->sendfilestate = PICA_CHANSENDFILESTATE_PAUSED;
senderctl = PICA_PROTO_FILECONTROL_PAUSE;
}
else
{
if (chan->recvfilestate != PICA_CHANRECVFILESTATE_RECEIVING)
return PICA_ERRINVARG;
chan->recvfilestate = PICA_CHANRECVFILESTATE_PAUSED;
receiverctl = PICA_PROTO_FILECONTROL_PAUSE;
}
return PICA_send_filecontrol(chan, senderctl, receiverctl);
}
int PICA_resume_file(struct PICA_c2c *chan, int sending)
{
int senderctl = PICA_PROTO_FILECONTROL_VOID;
int receiverctl = PICA_PROTO_FILECONTROL_VOID;
if (sending)
{
if (chan->sendfilestate != PICA_CHANSENDFILESTATE_PAUSED)
return PICA_ERRINVARG;
chan->sendfilestate = PICA_CHANSENDFILESTATE_SENDING;
senderctl = PICA_PROTO_FILECONTROL_RESUME;
}
else
{
if (chan->recvfilestate != PICA_CHANRECVFILESTATE_PAUSED)
return PICA_ERRINVARG;
chan->recvfilestate = PICA_CHANRECVFILESTATE_RECEIVING;
receiverctl = PICA_PROTO_FILECONTROL_RESUME;
}
return PICA_send_filecontrol(chan, senderctl, receiverctl);
}
int PICA_cancel_file(struct PICA_c2c *chan, int sending)
{
int senderctl = PICA_PROTO_FILECONTROL_VOID;
int receiverctl = PICA_PROTO_FILECONTROL_VOID;
if (sending)
{
if (chan->sendfilestate != PICA_CHANSENDFILESTATE_SENDING
&& chan->sendfilestate != PICA_CHANSENDFILESTATE_PAUSED)
return PICA_ERRINVARG;
fclose(chan->sendfile_stream);
chan->sendfile_stream = NULL;
chan->sendfilestate = PICA_CHANSENDFILESTATE_IDLE;
senderctl = PICA_PROTO_FILECONTROL_CANCEL;
}
else
{
if (chan->recvfilestate != PICA_CHANRECVFILESTATE_RECEIVING
&& chan->recvfilestate != PICA_CHANRECVFILESTATE_PAUSED)
return PICA_ERRINVARG;
fclose(chan->recvfile_stream);
chan->recvfile_stream = NULL;
chan->recvfilestate = PICA_CHANRECVFILESTATE_IDLE;
receiverctl = PICA_PROTO_FILECONTROL_CANCEL;
}
return PICA_send_filecontrol(chan, senderctl, receiverctl);
}
int PICA_send_filecontrol(struct PICA_c2c *chan, int senderctl, int receiverctl)
{
struct PICA_proto_msg *mp;
if ((mp = c2c_writebuf_push(chan, PICA_PROTO_FILECONTROL, PICA_PROTO_FILECONTROL_SIZE)))
{
mp->tail[0] = senderctl; //sender's command
mp->tail[1] = receiverctl; //receiver's command
}
else
return PICA_ERRNOMEM;
return PICA_OK;
}
int PICA_send_file_fragment(struct PICA_c2c *chn)
{
struct PICA_proto_msg *mp;
char buf[PICA_FILEFRAGMENTSIZE];
size_t fragment_size;
if (chn->sendfilestate != PICA_CHANSENDFILESTATE_SENDING)
return PICA_ERRFILETRANSFERNOTINPROGRESS;
fragment_size = fread(buf, 1, PICA_FILEFRAGMENTSIZE, chn->sendfile_stream);
if (fragment_size == 0)
{
fclose(chn->sendfile_stream);
chn->sendfile_stream = NULL;
chn->sendfilestate = PICA_CHANSENDFILESTATE_IDLE;
PICA_send_filecontrol(chn, PICA_PROTO_FILECONTROL_IOERROR, PICA_PROTO_FILECONTROL_VOID);
return PICA_ERRFILEIO;
}
if ((mp = c2c_writebuf_push(chn, PICA_PROTO_FILEFRAGMENT, fragment_size + 4)))
{
*((uint16_t*)mp->tail) = fragment_size;
memcpy(mp->tail + 2, buf, fragment_size);
}
else
{
return PICA_ERRNOMEM;
}
chn->sendfile_pos += fragment_size;
callbacks.file_progress_cb(chn->peer_id, chn->sendfile_pos, 0);
if (chn->sendfile_pos >= chn->sendfile_size)
{
chn->sendfilestate = PICA_CHANSENDFILESTATE_IDLE;
fclose(chn->sendfile_stream);
chn->sendfile_stream = NULL;
callbacks.file_finished_cb(chn->peer_id, 1);
}
if (chn->sendfile_pos > chn->sendfile_size)
return PICA_ERRFILEFRAGMENTCROSSEDSIZE;
return PICA_OK;
}
int PICA_start_call(struct PICA_c2c *chn)
{
struct PICA_proto_msg *mp;
if (chn->call_state != PICA_CALL_STATE_IDLE)
return PICA_ERRCALLINPROGRESS;
if ((mp = c2c_writebuf_push(chn, PICA_PROTO_CALLREQ, PICA_PROTO_CALLREQ_SIZE)))
{
RAND_pseudo_bytes(mp->tail, 2);
}
else
{
return PICA_ERRNOMEM;
}
chn->call_state = PICA_CALL_STATE_CALLING;
return PICA_OK;
}
int PICA_pickup_call(struct PICA_c2c *chn)
{
struct PICA_proto_msg *mp;
if (chn->call_state != PICA_CALL_STATE_INCOMINGCALL)
return PICA_ERRCALLNOTINPROGRESS;
if ((mp = c2c_writebuf_push(chn, PICA_PROTO_CALLANS, PICA_PROTO_CALLANS_SIZE)))
{
RAND_pseudo_bytes(mp->tail, 2);
}
else
{
return PICA_ERRNOMEM;
}
chn->call_state = PICA_CALL_STATE_ACTIVE;
return PICA_OK;
}
int PICA_reject_call(struct PICA_c2c *chn)
{
struct PICA_proto_msg *mp;
if (chn->call_state != PICA_CALL_STATE_INCOMINGCALL)
return PICA_ERRCALLNOTINPROGRESS;
if ((mp = c2c_writebuf_push(chn, PICA_PROTO_CALLREJ, PICA_PROTO_CALLREJ_SIZE)))
{
RAND_pseudo_bytes(mp->tail, 2);
}
else
{
return PICA_ERRNOMEM;
}
chn->call_state = PICA_CALL_STATE_IDLE;
return PICA_OK;
}
int PICA_hangup_call(struct PICA_c2c *chn)
{
struct PICA_proto_msg *mp;
if (chn->call_state == PICA_CALL_STATE_IDLE)
return PICA_ERRCALLNOTINPROGRESS;
if ((mp = c2c_writebuf_push(chn, PICA_PROTO_CALLFIN, PICA_PROTO_CALLFIN_SIZE)))
{
RAND_pseudo_bytes(mp->tail, 2);
}
else
{
return PICA_ERRNOMEM;
}
// release call resources??
// handle incoming last stream packets?
chn->call_state = PICA_CALL_STATE_IDLE;
return PICA_OK;
}
int PICA_set_call_audio_params(struct PICA_c2c *chn, const char *codec_name, uint16_t sample_rate)
{
struct PICA_proto_msg *mp;
if (chn->call_state != PICA_CALL_STATE_ACTIVE)
return PICA_ERRCALLNOTINPROGRESS;
if ((mp = c2c_writebuf_push(chn, PICA_PROTO_CALL_AUDIOPARAM, PICA_PROTO_CALL_AUDIOPARAM_SIZE)))
{
strncpy(mp->tail, codec_name, PICA_PROTO_CALL_CODECNAMESIZE);
*((uint16_t*)(mp->tail + PICA_PROTO_CALL_CODECNAMESIZE)) = sample_rate;
}
else
{
return PICA_ERRNOMEM;
}
return PICA_OK;
}
int PICA_set_call_video_params(struct PICA_c2c *chn, const char *codec_name, uint16_t width, uint16_t height)
{
struct PICA_proto_msg *mp;
if (chn->call_state != PICA_CALL_STATE_ACTIVE)
return PICA_ERRCALLNOTINPROGRESS;
if ((mp = c2c_writebuf_push(chn, PICA_PROTO_CALL_VIDEOPARAM, PICA_PROTO_CALL_VIDEOPARAM_SIZE)))
{
strncpy(mp->tail, codec_name, PICA_PROTO_CALL_CODECNAMESIZE);
*((uint16_t*)(mp->tail + PICA_PROTO_CALL_CODECNAMESIZE)) = width;
*((uint16_t*)(mp->tail + PICA_PROTO_CALL_CODECNAMESIZE + sizeof(width))) = height;
}
else
{
return PICA_ERRNOMEM;
}
return PICA_OK;
}
int PICA_send_audio_packet(struct PICA_c2c *chn, uint16_t seq_num, uint32_t timestamp, const char *buf, unsigned int len)
{
struct PICA_proto_msg *mp;
if (chn->call_state != PICA_CALL_STATE_ACTIVE)
return PICA_ERRCALLNOTINPROGRESS;
if ((mp = c2c_writebuf_push(chn, PICA_PROTO_CALL_AUDIO_PACKET, len + 4 + PICA_PROTO_CALL_PACKET_HDRSIZE)))
{
*((uint16_t*)mp->tail) = len + PICA_PROTO_CALL_PACKET_HDRSIZE;
*((uint16_t*)(mp->tail + 2)) = seq_num;
*((uint32_t*)(mp->tail + 2 + PICA_PROTO_CALL_PACKET_SEQNUM_SIZE)) = timestamp;
memcpy(mp->tail + 2 + PICA_PROTO_CALL_PACKET_HDRSIZE, buf, len);
}
else
{
return PICA_ERRNOMEM;
}
return PICA_OK;
}
int PICA_send_video_packet(struct PICA_c2c *chn, uint16_t seq_num, uint32_t timestamp, const char *buf, unsigned int len)
{
struct PICA_proto_msg *mp;
if (chn->call_state != PICA_CALL_STATE_ACTIVE)
return PICA_ERRCALLNOTINPROGRESS;
if ((mp = c2c_writebuf_push(chn, PICA_PROTO_CALL_VIDEO_PACKET, len + 4 + PICA_PROTO_CALL_PACKET_HDRSIZE)))
{
*((uint16_t*)mp->tail) = len + PICA_PROTO_CALL_PACKET_HDRSIZE;
*((uint16_t*)(mp->tail + 2)) = seq_num;
*((uint32_t*)(mp->tail + 2 + PICA_PROTO_CALL_PACKET_SEQNUM_SIZE)) = timestamp;
memcpy(mp->tail + 2 + PICA_PROTO_CALL_PACKET_HDRSIZE, buf, len);
}
else
{
return PICA_ERRNOMEM;
}
return PICA_OK;
}
void PICA_close_directc2c(struct PICA_directc2c *d)
{
PICA_TRACEFUNC
if (d->peer_cert)
X509_free(d->peer_cert);
if (d->addrlist)
free(d->addrlist);
SSL_free(d->ssl);
SHUTDOWN(d->sck);
CLOSE(d->sck);
d->next = NULL;
free(d);
}
void PICA_close_c2c(struct PICA_c2c *chn)
{
struct PICA_c2c *ipt;
PICA_TRACEFUNC
ipt = chn->conn->chan_list_head;
//puts("PICA_close_c2c_chkp0");//debug
while(ipt)
{
if (ipt == chn)
{
if (ipt->next)
ipt->next->prev = ipt->prev;
else
chn->conn->chan_list_end = ipt->prev;
if (ipt->prev)
ipt->prev->next = ipt->next;
else
chn->conn->chan_list_head = ipt->next;
break;
}
ipt = ipt->next;
}
//puts("PICA_close_c2c_chkp1");//debug
if (chn -> sendfile_stream)
fclose(chn -> sendfile_stream);
if (chn -> recvfile_stream)
fclose(chn -> recvfile_stream);
if (chn -> read_buf)
free(chn -> read_buf);
if (chn -> write_buf)
free(chn -> write_buf);
if (chn->peer_cert)
X509_free(chn->peer_cert);
//puts("PICA_close_c2c_chkp2");//debug
if (chn->ssl)
SSL_shutdown(chn->ssl);
//puts("PICA_close_c2c_chkp3");//debug
if (chn->ssl)
{
//printf("3 SSL_free(%X)\n",chn->ssl);//debug
SSL_free(chn->ssl);
}
//puts("PICA_close_c2c_chkp4");//debug
SHUTDOWN(chn->sck_data);
//puts("PICA_close_c2c_chkp5");//debug
CLOSE(chn->sck_data);
//puts("PICA_close_c2c_chkp6");//debug
if (chn->direct)
PICA_close_directc2c(chn->direct);
free(chn);
//puts("PICA_close_c2c_chkp7");//debug
}
void PICA_close_c2n(struct PICA_c2n *cid)
{
PICA_TRACEFUNC
struct PICA_c2c *ipt;
while((ipt = cid->chan_list_head))
PICA_close_c2c(ipt);
SSL_shutdown(cid->ssl_comm);
//printf("4 SSL_free(%X)\n",cid->ssl_comm);//debug
SSL_free(cid->ssl_comm);
SHUTDOWN(cid->sck_comm);
CLOSE(cid->sck_comm);
free(cid->read_buf);
if (cid->write_buf)
free(cid->write_buf);
free(cid);
}
void PICA_close_acc(struct PICA_acc *a)
{
SSL_CTX_free(a->ctx);
free(a->pkey_file);
free(a->cert_file);
free(a);
}
void PICA_close_listener(struct PICA_listener *l)
{
CLOSE(l->sck_listener);
free(l->public_addr_dns);
while(l->accepted_connections)
{
struct PICA_directc2c *c = l->accepted_connections;
l->accepted_connections = c->next;
PICA_close_directc2c(c);
}
free(l);
}
#ifdef WIN32
const char *PICA_inet_ntop(int af, const void *src, char *dst, size_t size)
{
unsigned char *chsrc = (unsigned char*)src;
switch(af)
{
case AF_INET:
sprintf(dst, "%u.%u.%u.%u", chsrc[0], chsrc[1], chsrc[2], chsrc[3]);
break;
case AF_INET6:
sprintf(dst, "%x%02x:%x%02x:%x%02x:%x%02x:%x%02x:%x%02x:%x%02x:%x%02x", chsrc[0], chsrc[1], chsrc[2], chsrc[3],
chsrc[4], chsrc[5], chsrc[6], chsrc[7],
chsrc[8], chsrc[9], chsrc[10], chsrc[11],
chsrc[12], chsrc[13], chsrc[14], chsrc[15]);
break;
default:
return NULL;
}
return dst;
}
#endif
int PICA_sendfile_open_read(struct PICA_c2c *chn, const char *filename_utf8, uint64_t *file_size)
{
#ifdef WIN32
{
wchar_t *wfilename = NULL;
int wlen;
wlen = MultiByteToWideChar(CP_UTF8, 0, filename_utf8, -1, NULL, 0);
if (wlen == 0 || wlen == 0xFFFD)
return PICA_ERRINVFILENAME;
wfilename = (wchar_t*)malloc(wlen * sizeof(wchar_t));
if (!wfilename)
return PICA_ERRNOMEM;
MultiByteToWideChar(CP_UTF8, 0, filename_utf8, -1, wfilename, wlen);
chn->sendfile_stream = _wfopen(wfilename, L"rb");
free(wfilename);
if (chn->sendfile_stream == NULL)
return PICA_ERRFILEOPEN;
if (_fseeki64(chn->sendfile_stream, 0, SEEK_END) != 0)
return PICA_ERRFILEOPEN;
*file_size = _ftelli64(chn->sendfile_stream);
if (_fseeki64(chn->sendfile_stream, 0, SEEK_SET) != 0)
return PICA_ERRFILEOPEN;
}
#else
chn->sendfile_stream = fopen(filename_utf8, "rb");
if (chn->sendfile_stream == NULL)
return PICA_ERRFILEOPEN;
if (fseeko(chn->sendfile_stream, 0, SEEK_END) != 0)
return PICA_ERRFILEOPEN;
*file_size = ftello(chn->sendfile_stream);
if (fseeko(chn->sendfile_stream, 0, SEEK_SET) != 0)
return PICA_ERRFILEOPEN;
#endif
return PICA_OK;
}
int PICA_recvfile_open_write(struct PICA_c2c *chn, const char *filename_utf8, unsigned int filenamesize)
{
/////////////
#ifdef WIN32
{
wchar_t *rfilename = NULL;
int rlen;
rlen = MultiByteToWideChar(CP_UTF8, 0, filename_utf8, filenamesize, NULL, 0);
if (rlen == 0 || rlen == 0xFFFD)
return PICA_ERRINVFILENAME;
rlen++;//space for terminating zero character
rfilename = malloc(rlen * sizeof(wchar_t));
if (!rfilename)
return PICA_ERRNOMEM;
MultiByteToWideChar(CP_UTF8, 0, filename_utf8, filenamesize, rfilename, rlen);
rfilename[rlen - 1] = L'\0';
chn->recvfile_stream = _wfopen(rfilename, L"wb");
free(rfilename);
if (chn->recvfile_stream == NULL)
return PICA_ERRFILEOPEN;
}
#else
char *filename = malloc(filenamesize + 1);
memcpy(filename, filename_utf8, filenamesize);
filename[filenamesize] = '\0';
chn->recvfile_stream = fopen(filename, "wb");
free(filename);
if (chn->recvfile_stream == NULL)
return PICA_ERRFILEOPEN;
#endif
/////////////
return PICA_OK;
}