You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
libsxmp/examples/smpfd.c

705 lines
18 KiB
C

/*
* Secure X Message Passing Library v2 examples.
*
* (c) Alexander Vdolainen 2013-2015 <avdolainen@gmail.com>
* (c) Alexander Vdolainen 2016 <avdolainen@zoho.com>
*
* libsxmp examples 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, either version 3 of the License, or
* (at your option) any later version.
*
* libsxmp 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 <http://www.gnu.org/licenses/>.";
*
*/
/*
* This is an example of sxmp usage.
* NOTE: It was developed quite fast within one day,
* btw - this the reason of some ugly code here.
*
* This implements a simple client-server topology, to see
* more advanced technics check out other examples.
*
* NOTE(win32): win32 is not a platform for any serious,
* except the games maybe... well, this code contains linux
* stuff (not sure about other *nixes)
*/
#include <stdio.h>
#include <dirent.h>
#define __USE_GNU
#include <stdlib.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <limits.h>
#include <sys/select.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <sys/resource.h>
#include <netdb.h>
#include <unistd.h>
#include <pwd.h>
#include <grp.h>
#include <execinfo.h>
#include <netinet/in.h>
#include <pthread.h>
#include <stdint.h>
#include <getopt.h>
#include <errno.h>
#include <uuid/uuid.h>
#include <tdata/usrtc.h>
#include <tdata/list.h>
#include <sexpr/sexp.h>
#include <sxmp/limits.h>
#include <sxmp/sxmp.h>
#include "smpf.h"
#include "smpfd_defs.h"
#include "../config.h"
#define FULL_PROGRAM_NAME "SMPF (secure message passing file) daemon"
#include "helpers.h"
static struct smpfd_ctx daemon_ctx;
/* firstly we need to take an rpc calls data,
* sxmp uses usrtc from libtdata to store it
*/
static usrtc_t *fulist;
/* misc functions */
static char *__humansize(struct stat *buf)
{
char *hsize = malloc(64);
int d = 0;
if(!hsize) return NULL;
if(buf->st_size < 10*1024) snprintf(hsize, 64, "%ld", buf->st_size);
else if(buf->st_size >= 10*1024 && buf->st_size < 1024*1024) { /* KiB */
d = buf->st_size % 1024;
if(!d) snprintf(hsize, 64, "%ldKiB", buf->st_size/1024);
else snprintf(hsize, 64, "%ld.%dKiB", buf->st_size/1024, d /= 102);
} else if(buf->st_size >= 1024*1024 && buf->st_size < 1024*1024*1024) { /* MiB */
d = buf->st_size % (1024*1024);
if(!d) snprintf(hsize, 64, "%ldMiB", buf->st_size/(1024*1024));
else snprintf(hsize, 64, "%ld.%dMiB", buf->st_size/(1024*1024), d /= 102);
} else { /* GiB */
d = buf->st_size % (1024*1024*1024);
if(!d) snprintf(hsize, 64, "%ldGiB", buf->st_size/(1024*1024*1024));
else snprintf(hsize, 64, "%ld.%dGiB", buf->st_size/(1024*1024*1024), d /= 102);
}
return hsize;
}
static char *__humanmode(struct stat *buf)
{
char *hmode = malloc(12);
char s = 0;
int i;
if(!hmode) return NULL;
memset(hmode, 0, 12);
for(i = 0; i < 10; i++) {
switch(i) {
case 0:
if(S_ISDIR(buf->st_mode)) s = 'd';
else if(S_ISCHR(buf->st_mode)) s = 'c';
else if(S_ISBLK(buf->st_mode)) s = 'b';
else if(S_ISFIFO(buf->st_mode)) s = 'f';
else if(S_ISLNK(buf->st_mode)) s = 'l';
else if(S_ISSOCK(buf->st_mode)) s = 's';
else s = '-';
break;
}
hmode[i] = s;
}
return hmode;
}
static char *__humanuser(struct stat *buf)
{
struct passwd pwd;
struct passwd *res;
char *pwdbuf, *huser;
size_t pwdlen;
if((pwdlen = sysconf(_SC_GETPW_R_SIZE_MAX)) == -1) pwdlen = 16384;
if(!(pwdbuf = malloc(pwdlen))) return NULL;
getpwuid_r(buf->st_uid, &pwd, pwdbuf, pwdlen, &res);
if(!res) {
if(!(huser = malloc(12))) goto __fini;
snprintf(huser, 12, "%d", buf->st_uid);
} else huser = strdup(pwd.pw_name);
__fini:
free(pwdbuf);
return huser;
}
static char *__humangroup(struct stat *buf)
{
struct group grp;
struct group *res;
char *pwdbuf, *hgroup;
size_t pwdlen;
if((pwdlen = sysconf(_SC_GETGR_R_SIZE_MAX)) == -1) pwdlen = 16384;
if(!(pwdbuf = malloc(pwdlen))) return NULL;
getgrgid_r(buf->st_gid, &grp, pwdbuf, pwdlen, &res);
if(!res) {
if(!(hgroup = malloc(12))) goto __fini;
snprintf(hgroup, 12, "%d", buf->st_gid);
} else hgroup = strdup(grp.gr_name);
__fini:
free(pwdbuf);
return hgroup;
}
/* ok, here we will define out sxmp specific functions */
/*
* Since we haven't any access restrictions in our example, just
* create a fake.
*/
static usrtc_t *__rettlist(sxlink_t *l)
{
return fulist;
}
/*
* Here we don't have those checks, just always accept
*/
static int __validate_sslpem(sxlink_t *l)
{
return 0;
}
/*
* don't check password and login,
* but assign session info for each link.
*/
static int __secure_check(sxlink_t *l)
{
struct mp_session *session;
if(!(session = malloc(sizeof(struct mp_session))))
return SXE_ENOMEM;
session->link = l;
strcpy(session->cwd, daemon_ctx.root_dir);
sxlink_setpriv(l, session);
return SXE_SUCCESS;
}
void __session_free(sxlink_t *l)
{
struct mp_session *session = (struct mp_session *)sxlink_getpriv(l);
if(session) free(session);
return;
}
/* remote functions implementation */
/* misc functions */
static int __ping(void *m, sexp_t *sx)
{
return sxmsg_return((sxmsg_t*)m, SXE_SUCCESS);
}
/* command line options helpers */
static void __help_print(FILE *fso, const char *fmtname)
{
fprintf(fso, "\n%s\n\n", FULL_PROGRAM_NAME);
/* usage options */
fprintf(fso, "Usage:\n");
fprintf(fso, "\t%s -r | --root-x509 <path> -u | --master-x509 <path> "
" [-p | --listen-port <port>] [-R | --root-dir <directory>] "
" [-d | --daemonize]\n", fmtname);
/* defaults */
fprintf(fso, "\t%s -h | --help\n", fmtname);
fprintf(fso, "\t%s -v | --version\n", fmtname);
/* options description */
fprintf(fso, "\nOptions:\n");
fprintf(fso, "\t%-33s Set pathname of the remote root X.509 public certificate.\n",
"-r | --root-x509 <path>");
fprintf(fso, "\t%-33s Set pathname of the host X.509 public and private certificate.\n",
"-m | --master-x509 <path>");
fprintf(fso, "\t%-33s Remote host listening port number [default: %d].\n",
"-p | --listen-port <port>", DEFAULT_PORT);
fprintf(fso, "\t%-33s Remote directory becoming root for user [default: %s].\n",
"-R | --root-dir <directory>", DEFAULT_ROOT_DIR);
fprintf(fso, "\t%-33s Work as a daemon.\n", "-d | --daemonize");
fprintf(fso, "\t%-33s Show help screen.\n", "-h | --help");
fprintf(fso, "\t%-33s Print version information.\n", "-v | --version");
return;
}
/* control operations */
static int __setcwd(void *m, sexp_t *sx)
{
sxmsg_t *msg = (sxmsg_t*)m;
sxlink_t *link = sxmsg_link(msg);
char *dirname = NULL, *odir = NULL;
sexp_t *isx;
int r = SXE_SUCCESS, idx, olen = 0;
struct mp_session *session = (struct mp_session *)sxlink_getpriv(link);
SEXP_ITERATE_LIST(sx, isx, idx) {
if(isx->ty == SEXP_LIST) r = SXE_BADPROTO;
if(idx > 1) r = SXE_BADPROTO;
if(idx) dirname = isx->val;
}
if(r != SXE_SUCCESS || !dirname) goto __fini;
if(!strcmp(dirname, "./")) goto __fini;
if(!strlen(dirname) || !strcmp(dirname, ".")) goto __fini;
if(*dirname == '/') odir = strdup(dirname);
else {
olen = strlen(session->cwd) + strlen(dirname) + 2*sizeof(char);
odir = malloc(olen);
}
if(!odir) {
r = SXE_ENOMEM;
goto __fini;
}
if(olen) snprintf(odir, olen, "%s/%s", session->cwd, dirname);
if(normalize_path(odir)) r = SXE_FAILED;
else strcpy(session->cwd, odir);
__fini:
if(odir) free(odir);
return sxmsg_return(msg, r);
}
/* readonly operations */
static int __getcwd(void *m, sexp_t *sx)
{
sxmsg_t *msg = (sxmsg_t*)m;
sxlink_t *link = sxmsg_link(msg);
char *rbuf = sxmsg_rapidbuf(msg);
size_t rbuf_len = 0;
struct mp_session *session = (struct mp_session *)sxlink_getpriv(link);
rbuf_len = snprintf(rbuf, MAX_RBBUF_LEN, "(\"%s\")", session->cwd);
return sxmsg_rreply(msg, rbuf_len);
}
static int __stat(void *m, sexp_t *sx)
{
sxmsg_t *msg = (sxmsg_t*)m;
sxlink_t *link = sxmsg_link(msg);
char *rbuf = sxmsg_rapidbuf(msg), *dirname = NULL, *odir = NULL;
char *hsize, *hmode, *huser, *hgroup;
sexp_t *isx;
size_t rbuf_len = 0;
int r = SXE_SUCCESS, idx, olen = 0, rc;
struct mp_session *session = (struct mp_session *)sxlink_getpriv(link);
struct stat statbuf;
SEXP_ITERATE_LIST(sx, isx, idx) {
if(isx->ty == SEXP_LIST) r = SXE_BADPROTO;
if(idx > 1) r = SXE_BADPROTO;
if(idx) dirname = isx->val;
}
if(r != SXE_SUCCESS || !dirname) goto __fail;
if(!strcmp(dirname, "./")) goto __fail;
if(!strlen(dirname) || !strcmp(dirname, ".")) goto __fail;
if(*dirname == '/') odir = strdup(dirname);
else {
olen = strlen(session->cwd) + strlen(dirname) + 2*sizeof(char);
odir = malloc(olen);
}
if(!odir) {
r = SXE_ENOMEM;
goto __fail;
}
if(olen) snprintf(odir, olen, "%s/%s", session->cwd, dirname);
if(normalize_path(odir)) r = SXE_FAILED;
else { /* take stat of the file */
rc = stat(odir, &statbuf);
if(rc) {
r = SXE_FAILED;
goto __fail;
}
/* make this human readable to reply for client */
hsize = __humansize(&statbuf);
hmode = __humanmode(&statbuf);
huser = __humanuser(&statbuf);
hgroup = __humangroup(&statbuf);
/* create a reply */
rbuf_len = snprintf(rbuf, MAX_RBBUF_LEN, "((:user %s)(:group %s)(:size %s)(:mode %s))",
huser, hgroup, hsize, hmode);
/* free */
if(odir) free(odir);
if(hsize) free(hsize);
if(hmode) free(hmode);
if(huser) free(huser);
if(hgroup) free(hgroup);
/* reply rapidly */
return sxmsg_rreply(msg, rbuf_len);
}
__fail:
if(odir) free(odir);
return sxmsg_return(msg, r);
}
/* stream, simple entry nonamed streams */
/* dir listing stream */
typedef struct __dirlist_type {
DIR *dp;
struct dirent dent;
struct dirent *dres;
} dirlist_t;
static int dirlist_open(sxlink_t *link, struct sxstream_opened *stream,
const char *dirname)
{
dirlist_t *pslist = malloc(sizeof(dirlist_t));
char *odir = NULL;
struct mp_session *session = (struct mp_session *)sxlink_getpriv(link);
int olen = 0;
if(!pslist) return SXE_ENOMEM;
else memset(pslist, 0, sizeof(dirlist_t));
if(!dirname) odir = session->cwd;
else if(*dirname == '/') odir = (char *)dirname;
else {
olen = strlen(dirname) + strlen(session->cwd) + 2*sizeof(char);
if(!(odir = malloc(olen))) {
free(pslist);
return SXE_ENOMEM;
}
snprintf(odir, olen, "%s/%s", session->cwd, dirname);
}
if(!(pslist->dp = opendir(odir))) {
if(dirname && *dirname != '/') free(odir);
free(pslist);
return SXE_FAILED;
} else sxstream_opened_setpriv(stream, pslist);
if(dirname && *dirname != '/') free(odir);
return SXE_SUCCESS;
}
static int dirlist_close(struct sxstream_opened *stream)
{
dirlist_t *pslist = sxstream_opened_getpriv(stream);
list_head_t *llist = sxstream_opened_getelist(stream);
if(!pslist) return SXE_FAILED;
if(llist) sxstream_generic_slist_free(llist);
/* close dir and free strcuture */
if(pslist->dp) closedir(pslist->dp);
free(pslist);
return SXE_SUCCESS;
}
static inline const char *dump_type(struct dirent *d)
{
if(!d) return "nil";
switch(d->d_type) {
case DT_REG: return "regular";
case DT_DIR: return "directory";
case DT_BLK: return "block";
case DT_CHR: return "char";
case DT_FIFO: return "fifo";
case DT_LNK: return "link";
case DT_UNKNOWN: return "unknown";
default: return "another";
}
return "nil";
}
static size_t dirlist_read(struct sxstream_opened *stream, size_t sz, uint64_t off,
list_head_t **rlist)
{
dirlist_t *pslist = sxstream_opened_getpriv(stream);
list_head_t *llist_prev = sxstream_opened_getelist(stream);
list_head_t *llist_new = NULL;
if(!pslist) return -1;
/* read and fill new list */
if(readdir_r(pslist->dp, &(pslist->dent), &(pslist->dres))) return -1;
/* if finished return 0 */
if(!pslist->dres) {
if(llist_prev) sxstream_generic_slist_free(llist_prev);
stream->ent_buf = NULL;
return 0;
}
/* allocate a new */
if(!(llist_new = sxstream_generic_slist_alloc())) return -1;
else { /* fill this list */
if(sxstream_generic_slist_additem(llist_new, pslist->dres->d_name)) {
__fail:
sxstream_generic_slist_free(llist_new);
return -1;
}
if(sxstream_generic_slist_additem(llist_new, dump_type(pslist->dres)))
goto __fail;
}
/* otherwise destroy last list */
if(llist_prev) sxstream_generic_slist_free(llist_prev);
*rlist = llist_new; /* assign new list */
return 1;
}
static struct sxstream_ops dirlist_ops = {
.s_open = dirlist_open,
.s_close = dirlist_close,
.s_eread = dirlist_read,
.s_ewrite = NULL,
};
static struct sxstream_description dirlist_stream = {
.stid = DIRLIST_ID,
.pcid = READONLY_CHANNEL,
.type = 0, /* just a simple one */
.flags = SXE_O_READ | SXE_O_TRUNC,
.ops = &dirlist_ops,
};
int main(int argc, char **argv)
{
char *rootca = NULL, *cert = NULL;
char *root_dir = NULL;
sxhub_t *sxmphub = sxhub_create();
int port = DEFAULT_PORT;
int opt;
int is_daemon = 0;
while(1) {
int option_index = 0;
static struct option long_options[] = {
/* These options a generic ones. */
{"help", no_argument, NULL, 'h'}, /* print out help and version info */
{"version", no_argument, NULL, 'v'}, /* just out a version info */
/* The following options about settings . */
{"root-x509", required_argument, NULL, 'r'},
{"master-x509", required_argument, NULL, 'm'},
{"root-dir", required_argument, NULL, 'R'},
{"listen-port", required_argument, NULL, 'p'},
{"daemonize", required_argument, NULL, 'd'},
{ NULL, 0, NULL, 0 },
};
if((opt = getopt_long(argc, argv, "dhvr:m:R:p:", long_options,
&option_index)) == -1) break;
switch(opt) {
case 'h':
__help_print(stdout, argv[0]);
return 0;
break;
case 'v':
version_print(stdout);
return 0;
break;
case 'r':
if(!(rootca = strdup(optarg))) return ENOMEM;
break;
case 'm':
if(!(cert = strdup(optarg))) return ENOMEM;
break;
case 'p':
port = atoi(optarg);
break;
case 'R':
root_dir = strdup(optarg);
break;
case 'd':
is_daemon = 1;
break;
}
}
if(!rootca) {
fprintf(stderr, "Root CA not pointed.\n Failure.\n");
__help_print(stderr, argv[0]);
return EINVAL;
}
if(!cert) {
fprintf(stderr, "User certificate not pointed.\n Failure.\n");
__help_print(stderr, argv[0]);
return EINVAL;
}
/* become daemon if it's necessary */
if (is_daemon && (opt = daemonize())) {
fprintf(stderr, "daemonize() failed (%d).\n Failure.\n", opt);
return EINVAL;
}
sxmp_init();
/* all is fine let's init connection subsystem */
if(!sxmphub) {
fprintf(stderr, "Subsystem init failed: %d\n", opt);
return 2;
}
/* root directory */
if(!root_dir) {
root_dir = strdup(DEFAULT_ROOT_DIR);
if(!root_dir) {
fprintf(stderr, "Not enough memory.\n");
return 2;
}
}
/* set working certificates */
opt = sxhub_setsslserts(sxmphub, rootca, cert, cert);
if(opt) {
fprintf(stderr, "Subsystem init failed (set SSL x.509 pems): %d\n", opt);
return opt;
}
/* clean up */
free(rootca);
free(cert);
/* set context */
daemon_ctx.root_dir = root_dir;
daemon_ctx.root = NULL;
daemon_ctx.hostname = nodehostname();
/* set important callbacks to do the security checking */
sxhub_set_authcheck(sxmphub, __secure_check);
sxhub_set_sslvalidate(sxmphub, __validate_sslpem);
/* we using our internal session info */
sxhub_set_ondestroy(sxmphub, __session_free);
/* ok, now we need to construct RPC lists (channels) */
if(!(fulist = malloc(sizeof(usrtc_t)))) {
fprintf(stderr, "Cannot allocate memory for RPC lists\n Failure.\n");
return ENOMEM;
}
opt = sxmp_rpclist_init(fulist);
if(opt) {
fprintf(stderr, "Failed to init rpc list\n Failure.\n");
return opt;
}
/*
* Public channel
*/
opt = sxmp_rpclist_add(fulist, PUBLIC_CHANNEL, "Public channel", NULL);
if(opt) {
__failaddchannel:
fprintf(stderr, "Failed to add typed RPC channel\n Failure.\n");
return opt;
}
opt = sxmp_rpclist_add_function(fulist, PUBLIC_CHANNEL, PING_CMD, __ping);
if(opt) {
__failaddrpc:
fprintf(stderr, "Failed to add functions to typed RPC channel\n Failure.\n");
return opt;
}
/*
* Readonly channel
*/
opt = sxmp_rpclist_add(fulist, READONLY_CHANNEL, "Read only operations", NULL);
if(opt) goto __failaddchannel;
/* ok, let's add read functions */
opt = sxmp_rpclist_add_function(fulist, READONLY_CHANNEL, CWD_CMD, __setcwd);
if(opt) goto __failaddrpc;
opt = sxmp_rpclist_add_function(fulist, READONLY_CHANNEL, GETCWD_CMD, __getcwd);
if(opt) goto __failaddrpc;
opt = sxmp_rpclist_add_function(fulist, READONLY_CHANNEL, STAT_CMD, __stat);
if(opt) goto __failaddrpc;
/* streams */
sxhub_stream_register(sxmphub, &dirlist_stream);
/*
* Write channel
*/
opt = sxmp_rpclist_add(fulist, WRITEONLY_CHANNEL, "Write only operations", NULL);
if(opt) goto __failaddchannel;
/* functions */
opt = sxmp_rpclist_add_function(fulist, WRITEONLY_CHANNEL, CREAT_CMD, __ping);
if(opt) goto __failaddrpc;
opt = sxmp_rpclist_add_function(fulist, WRITEONLY_CHANNEL, MKDIR_CMD, __ping);
if(opt) goto __failaddrpc;
opt = sxmp_rpclist_add_function(fulist, WRITEONLY_CHANNEL, RMDIR_CMD, __ping);
if(opt) goto __failaddrpc;
opt = sxmp_rpclist_add_function(fulist, WRITEONLY_CHANNEL, UNLINK_CMD, __ping);
if(opt) goto __failaddrpc;
/* ok, setup it */
sxhub_set_rpcvalidator(sxmphub, __rettlist);
signal(SIGPIPE, SIG_IGN);
/* now we're ready to run the listen process */
int srv = openlistener_socket(port);
while(1) {
struct sockaddr_in addr;
socklen_t len = sizeof(addr);
sxlink_t *link;
int client = accept(srv, (struct sockaddr*)&addr, &len); /* accept connection as usual */
link = sxlink_master_accept(sxmphub, client, NULL);
if(!link) fprintf(stderr, "Cannot create sxmp link (%d)\n", errno);
}
sxhub_destroy(sxmphub);
sxmp_finalize();
return 0;
}