/* 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 #include #include #include #include #include #include #include #include #include #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; }