/* * Secure X Message Passing Library v2 examples. * * (c) Alexander Vdolainen 2013-2015 * (c) Alexander Vdolainen 2016 * * 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 ."; * */ /* * 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 #include #define __USE_GNU #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 -u | --master-x509 " " [-p | --listen-port ] [-R | --root-dir ] " " [-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 "); fprintf(fso, "\t%-33s Set pathname of the host X.509 public and private certificate.\n", "-m | --master-x509 "); fprintf(fso, "\t%-33s Remote host listening port number [default: %d].\n", "-p | --listen-port ", DEFAULT_PORT); fprintf(fso, "\t%-33s Remote directory becoming root for user [default: %s].\n", "-R | --root-dir ", 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; }