pcloudcc-lneely/pclsync/prpc.c

344 lines
11 KiB
C

/*
Copyright (c) 2013-2015 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.
*/
/*
poverlay is the "overlay server" (contrast with overlay_client). It
listens for request messages generated by overlay_client on a unix
socket, invokes the appropriate callback based on the message type,
and generates a response message. It then writes the response
message back to the socket where the overlay_client will read and
act upon it.
*/
#include <errno.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h>
#include "pdbg.h"
#include "pmem.h"
#include "ppath.h"
#include "ppathstatus.h"
#include "prpc.h"
#include "prun.h"
#define POVERLAY_BUFSIZE 512
static int overlays_running = 1;
static int handlers_running = 1;
static void respond(rpc_message_t*, rpc_message_t*);
static void on_request(void *lpvParam);
prpc_handler *handlers = NULL;
static int handlers_size = 15;
static const int calbacks_lower_band = 20;
static void on_request(void *lpvParam) {
int *sockfd; // pcloud socket file descriptor
int rc; // bytes read / written per iteration
int readbytes; // total bytes read from request
char rqbuf[POVERLAY_BUFSIZE]; // request buffer, contains the request message
char *rqbufp; // request buffer ptr, for convenient iteration
rpc_message_t *request; // request message
rpc_message_t *response; // response message and payload
request = NULL;
response = NULL;
readbytes = 0;
rqbufp = &rqbuf[0];
sockfd = (int *)lpvParam;
// read the request from the socket into the request buffer
memset(rqbuf, 0, POVERLAY_BUFSIZE);
while ((rc = read(*sockfd, rqbufp, (POVERLAY_BUFSIZE - readbytes))) > 0) {
readbytes += rc;
rqbufp = rqbufp + rc;
if (readbytes > 12) {
request = (rpc_message_t *)rqbuf;
if (request->length > POVERLAY_BUFSIZE) {
pdbg_logf(D_ERROR, "Request length %lu exceeds buffer size %d",
(unsigned long)request->length, POVERLAY_BUFSIZE);
goto cleanup;
}
if (request->length == (uint64_t)readbytes)
break;
}
}
if (rc == -1) {
pdbg_logf(D_ERROR, "Unix socket read error");
goto cleanup;
} else if (rc == 0 && readbytes == 0) {
pdbg_logf(D_NOTICE, "Connection closed by client before sending data");
goto cleanup;
}
request = (rpc_message_t *)rqbuf;
response = (rpc_message_t *)pmem_malloc(PMEM_SUBSYS_OTHER, POVERLAY_BUFSIZE);
if (request) {
respond(request, response);
ssize_t total_size = offsetof(rpc_message_t, value) + response->length;
ssize_t bytes_written = write(*sockfd, response, total_size);
if (bytes_written == -1) {
pdbg_logf(D_ERROR, "Failed to write to socket: %s", strerror(errno));
return;
} else if (bytes_written < total_size) {
pdbg_logf(D_ERROR, "Incomplete write to socket: wrote %zd of %zd bytes", bytes_written, total_size);
return;
}
pdbg_logf(D_NOTICE, "Successfully wrote %zd bytes to socket", bytes_written);
} else {
pdbg_logf(D_ERROR, "No valid request received");
}
cleanup:
if (sockfd) {
close(*sockfd);
pmem_free(PMEM_SUBSYS_OTHER, sockfd);
}
if (response) {
pmem_free(PMEM_SUBSYS_OTHER, response);
}
pdbg_logf(D_NOTICE, "InstanceThread exiting.");
}
static void respond_status(rpc_message_t *request, rpc_message_t *response, size_t available_space) {
psync_path_status_t stat;
stat = PSYNC_PATH_STATUS_NOT_OURS;
if (overlays_running) {
stat = ppathstatus_get(request->value);
}
switch (ppathstatus_get_status(stat)) {
case PSYNC_PATH_STATUS_IN_SYNC:
response->type = 10;
break;
case PSYNC_PATH_STATUS_IN_PROG:
response->type = 12;
break;
case PSYNC_PATH_STATUS_PAUSED:
case PSYNC_PATH_STATUS_REMOTE_FULL:
case PSYNC_PATH_STATUS_LOCAL_FULL:
response->type = 11;
break;
default:
response->type = 13;
snprintf(response->value, available_space, "No.");
}
}
static void respond_api(rpc_message_t *request, rpc_message_t *response, size_t available_space) {
int cbidx; // callback index (based on message type)
int cbret; // callback return value
if (!handlers_running || request->type < (uint32_t)calbacks_lower_band ||
request->type >= ((uint32_t)calbacks_lower_band + (uint32_t)handlers_size)) {
response->type = 13;
snprintf(response->value, available_space, "Invalid type.");
pdbg_logf(D_NOTICE, "Invalid request type: %u", request->type);
return;
}
cbidx = request->type - calbacks_lower_band;
cbret = 0;
if (!handlers[cbidx]) {
response->type = 13;
snprintf(response->value, available_space,
"No callback with this id registered.");
pdbg_logf(D_NOTICE, "No callback registered for type: %u", request->type);
return;
}
cbret = handlers[cbidx](request->value);
if (cbret == 0) {
response->type = 0;
} else {
response->type = cbret;
snprintf(response->value, available_space,
"Callback returned error code.");
pdbg_logf(D_NOTICE, "Callback failed with return code: %d", cbret);
}
}
static void respond(rpc_message_t *request, rpc_message_t *response) {
const char *dbgmsg; // debug messages
size_t value_avail; // space available to store value (flexible array)
dbgmsg = NULL;
memset(response, 0, POVERLAY_BUFSIZE);
response->length = 0;
value_avail = POVERLAY_BUFSIZE - sizeof(rpc_message_t);
// never print the crypto password to the logs in plain text
dbgmsg = (request->type == 20) ? "REDACTED" : request->value;
pdbg_logf(D_NOTICE, "Client Request type [%u] len [%lu] string: [%s]", request->type, request->length, dbgmsg);
if (request->type < 20) {
respond_status(request, response, value_avail);
} else {
respond_api(request, response, value_avail);
}
// a message with a type != 13 and a null string value after
// processing is considered successful. set value to "Ok."
if (response->type != 13 && response->value[0] == '\0') {
snprintf(response->value, value_avail, "Ok.");
} else {
pdbg_logf(D_WARNING, "not updating value to Ok: response->msg->type=%d, " "response->msg->value=%s", response->type, response->value);
}
// truncate messages that exceed the buffer boundaries
size_t value_length = strnlen(response->value, value_avail);
if (value_length > POVERLAY_BUFSIZE - sizeof(rpc_message_t) - 1) {
value_length = POVERLAY_BUFSIZE - sizeof(rpc_message_t) - 1;
response->value[value_length] = '\0';
pdbg_logf(D_WARNING, "Response message truncated to fit buffer");
}
response->length = offsetof(rpc_message_t, value) + value_length + 1;
}
void prpc_main_loop() {
struct sockaddr_un addr;
int fd, cl;
char *sockpath = prpc_sockpath();
if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
pdbg_logf(D_ERROR, "Unix socket error failed to open %s", sockpath);
pmem_free(PMEM_SUBSYS_OTHER, sockpath);
return;
}
if (fchmod(fd, 0600) == -1) {
pdbg_logf(D_ERROR, "Failed to set socket permissions");
pmem_free(PMEM_SUBSYS_OTHER, sockpath);
return;
}
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, sockpath, sizeof(addr.sun_path) - 1);
unlink(sockpath);
if (bind(fd, (struct sockaddr *)&addr, strlen(sockpath) + sizeof(addr.sun_family)) == -1) {
pdbg_logf(D_ERROR, "Unix socket bind error");
pmem_free(PMEM_SUBSYS_OTHER, sockpath);
return;
}
pmem_free(PMEM_SUBSYS_OTHER, sockpath);
if (listen(fd, 5) == -1) {
pdbg_logf(D_ERROR, "Unix socket listen error");
return;
}
while (1) {
if ((cl = accept(fd, NULL, NULL)) == -1) {
pdbg_logf(D_ERROR, "Unix socket accept error");
continue;
}
int *cl_ptr = pmem_malloc(PMEM_SUBSYS_OTHER, sizeof(int));
if (!cl_ptr) {
pdbg_logf(D_ERROR, "Failed to allocate memory for socket fd");
close(cl);
continue;
}
*cl_ptr = cl;
// handle the request in a new thread
prun_thread1("Pipe request handle routine",
on_request, // thread proc
(void *)cl_ptr // thread parameter
);
}
return;
}
int prpc_register(int cmdid, prpc_handler h) {
prpc_handler *handlers_old = handlers;
int handlers_size_old = handlers_size;
if (cmdid < calbacks_lower_band)
return -1;
if (cmdid > (calbacks_lower_band + handlers_size)) {
handlers_size = cmdid - calbacks_lower_band + 1;
prpc_init();
if (!handlers) {
handlers = handlers_old;
handlers_size = handlers_size_old;
return -1;
}
memcpy(handlers, handlers_old,
handlers_size_old * sizeof(prpc_handler));
pmem_free(PMEM_SUBSYS_OTHER, handlers_old);
}
handlers[cmdid - calbacks_lower_band] = h;
return 0;
}
void prpc_init() {
handlers = (prpc_handler *)pmem_malloc(PMEM_SUBSYS_OTHER, sizeof(prpc_handler) * handlers_size);
if (!handlers) {
pdbg_logf(D_ERROR, "Failed to allocate handler table");
return;
}
memset(handlers, 0, sizeof(prpc_handler) * handlers_size);
}
char *prpc_sockpath() {
char *home = ppath_home();
if (!home) {
return NULL;
}
const char *subdir = "/.pcloud/prpc.sock";
size_t len = strlen(home) + strlen(subdir) + 1;
char *sockpath = (char *)pmem_malloc(PMEM_SUBSYS_OTHER, len);
if (!sockpath) {
return NULL;
}
snprintf(sockpath, len, "%s%s", home, subdir);
pmem_free(PMEM_SUBSYS_OTHER, home);
return sockpath;
}