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

689 lines
14 KiB

/* -- sslcmds.c -- */
#include "x11vnc.h"
#include "inet.h"
#include "cleanup.h"
#include "sslhelper.h"
#include "ssltools.h"
#if LIBVNCSERVER_HAVE_FORK
#if LIBVNCSERVER_HAVE_SYS_WAIT_H
#if LIBVNCSERVER_HAVE_WAITPID
#define SSLCMDS
#endif
#endif
#endif
void check_stunnel(void);
int start_stunnel(int stunnel_port, int x11vnc_port);
void stop_stunnel(void);
void setup_stunnel(int rport, int *argc, char **argv);
char *get_Cert_dir(char *cdir_in, char **tmp_in);
void sslGenCA(char *cdir);
void sslGenCert(char *ty, char *nm);
void sslEncKey(char *path, int info_only);
static pid_t stunnel_pid = 0;
void check_stunnel(void) {
static time_t last_check = 0;
time_t now = time(0);
if (last_check + 3 >= now) {
return;
}
last_check = now;
/* double check that stunnel is still running: */
if (stunnel_pid > 0) {
int status;
waitpid(stunnel_pid, &status, WNOHANG);
if (kill(stunnel_pid, 0) != 0) {
waitpid(stunnel_pid, &status, WNOHANG);
rfbLog("stunnel subprocess %d died.\n", stunnel_pid);
stunnel_pid = 0;
clean_up_exit(1);
}
}
}
int start_stunnel(int stunnel_port, int x11vnc_port) {
#ifdef SSLCMDS
char extra[] = ":/usr/sbin:/usr/local/sbin:/dist/sbin";
char *path, *p, *exe;
char *stunnel_path = NULL;
struct stat verify_buf;
int status;
if (stunnel_pid) {
stop_stunnel();
}
stunnel_pid = 0;
path = getenv("PATH");
if (! path) {
path = strdup(extra);
} else {
path = (char *) malloc(strlen(path)+strlen(extra)+1);
if (! path) {
return 0;
}
strcpy(path, getenv("PATH"));
strcat(path, extra);
}
exe = (char *) malloc(strlen(path) + strlen("stunnel") + 1);
p = strtok(path, ":");
exe[0] = '\0';
while (p) {
struct stat sbuf;
sprintf(exe, "%s/%s", p, "stunnel");
if (! stunnel_path && stat(exe, &sbuf) == 0) {
if (! S_ISDIR(sbuf.st_mode)) {
stunnel_path = exe;
break;
}
}
p = strtok(NULL, ":");
}
if (path) {
free(path);
}
if (! stunnel_path) {
return 0;
}
if (stunnel_path[0] == '\0') {
free(stunnel_path);
return 0;
}
if (no_external_cmds) {
rfbLogEnable(1);
rfbLog("start_stunnel: cannot run external commands in -nocmds mode:\n");
rfbLog(" \"%s\"\n", stunnel_path);
rfbLog(" exiting.\n");
clean_up_exit(1);
}
if (! quiet) {
rfbLog("\n");
rfbLog("starting ssl tunnel: %s %d -> %d\n", stunnel_path,
stunnel_port, x11vnc_port);
}
if (stunnel_pem && strstr(stunnel_pem, "SAVE") == stunnel_pem) {
stunnel_pem = get_saved_pem(stunnel_pem, 1);
if (! stunnel_pem) {
rfbLog("start_stunnel: could not create or open"
" saved PEM:\n", stunnel_pem);
clean_up_exit(1);
}
}
if (ssl_verify) {
if (stat(ssl_verify, &verify_buf) != 0) {
rfbLog("stunnel: %s does not exist.\n", ssl_verify);
return 0;
}
}
stunnel_pid = fork();
if (stunnel_pid < 0) {
stunnel_pid = 0;
free(stunnel_path);
return 0;
}
if (stunnel_pid == 0) {
FILE *in;
char fd[20];
int i;
for (i=3; i<256; i++) {
close(i);
}
if (use_stunnel == 3) {
char sp[20], xp[20], *a = NULL;
char *st = stunnel_path;
char *pm = stunnel_pem;
char *sv = ssl_verify;
sprintf(sp, "%d", stunnel_port);
sprintf(xp, "%d", x11vnc_port);
if (ssl_verify) {
if(S_ISDIR(verify_buf.st_mode)) {
a = "-a";
} else {
a = "-A";
}
}
if (stunnel_pem && ssl_verify) {
execlp(st, st, "-f", "-d", sp, "-r", xp, "-P",
"none", "-p", pm, a, sv, "-v", "2",
(char *) NULL);
} else if (stunnel_pem && !ssl_verify) {
execlp(st, st, "-f", "-d", sp, "-r", xp, "-P",
"none", "-p", pm,
(char *) NULL);
} else if (!stunnel_pem && ssl_verify) {
execlp(st, st, "-f", "-d", sp, "-r", xp, "-P",
"none", a, sv, "-v", "2",
(char *) NULL);
} else {
execlp(st, st, "-f", "-d", sp, "-r", xp, "-P",
"none", (char *) NULL);
}
exit(1);
}
in = tmpfile();
if (! in) {
exit(1);
}
fprintf(in, "foreground = yes\n");
fprintf(in, "pid =\n");
if (stunnel_pem) {
fprintf(in, "cert = %s\n", stunnel_pem);
}
if (ssl_verify) {
if(S_ISDIR(verify_buf.st_mode)) {
fprintf(in, "CApath = %s\n", ssl_verify);
} else {
fprintf(in, "CAfile = %s\n", ssl_verify);
}
fprintf(in, "verify = 2\n");
}
fprintf(in, ";debug = 7\n\n");
fprintf(in, "[x11vnc_stunnel]\n");
fprintf(in, "accept = %d\n", stunnel_port);
fprintf(in, "connect = %d\n", x11vnc_port);
fflush(in);
rewind(in);
sprintf(fd, "%d", fileno(in));
execlp(stunnel_path, stunnel_path, "-fd", fd, (char *) NULL);
exit(1);
}
free(stunnel_path);
usleep(500 * 1000);
waitpid(stunnel_pid, &status, WNOHANG);
if (kill(stunnel_pid, 0) != 0) {
waitpid(stunnel_pid, &status, WNOHANG);
stunnel_pid = 0;
return 0;
}
if (! quiet) {
rfbLog("stunnel pid is: %d\n", (int) stunnel_pid);
}
return 1;
#else
return 0;
#endif
}
void stop_stunnel(void) {
int status;
if (! stunnel_pid) {
return;
}
#ifdef SSLCMDS
kill(stunnel_pid, SIGTERM);
usleep (150 * 1000);
kill(stunnel_pid, SIGKILL);
usleep (50 * 1000);
waitpid(stunnel_pid, &status, WNOHANG);
#endif
stunnel_pid = 0;
}
void setup_stunnel(int rport, int *argc, char **argv) {
int i, xport = 0;
if (! rport) {
for (i=0; i< *argc; i++) {
if (!strcmp(argv[i], "-rfbport")) {
if (i < *argc - 1) {
rport = atoi(argv[i+1]);
break;
}
}
}
}
if (! rport) {
/* we do our own autoprobing then... */
rport = find_free_port(5900, 5999);
if (! rport) {
goto stunnel_fail;
}
}
xport = find_free_port(5950, 5999);
if (! xport) {
goto stunnel_fail;
}
if (start_stunnel(rport, xport)) {
int tweaked = 0;
char tmp[10];
sprintf(tmp, "%d", xport);
if (argv) {
for (i=0; i< *argc; i++) {
if (!strcmp(argv[i], "-rfbport")) {
if (i < *argc - 1) {
argv[i+i] = strdup(tmp);
tweaked = 1;
break;
}
}
}
if (! tweaked) {
i = *argc;
argv[i] = strdup("-rfbport");
argv[i+1] = strdup(tmp);
*argc += 2;
got_rfbport = 1;
}
}
stunnel_port = rport;
ssl_initialized = 1;
return;
}
stunnel_fail:
rfbLog("failed to start stunnel.\n");
clean_up_exit(1);
}
char *get_Cert_dir(char *cdir_in, char **tmp_in) {
char *cdir, *home, *tmp;
struct stat sbuf;
int i;
char *cases1[] = {"/.vnc", "/.vnc/certs", "/.vnc/certs/CA"};
char *cases2[] = {"", "/CA", "/tmp"};
if (cdir_in != NULL) {
cdir = cdir_in;
} else {
cdir = ssl_certs_dir;
}
if (cdir == NULL) {
home = get_home_dir();
if (! home) {
return NULL;
}
cdir = (char *) malloc(strlen(home) + strlen("/.vnc/certs/CA") + 1);
for (i=0; i<3; i++) {
sprintf(cdir, "%s%s", home, cases1[i]);
if (stat(cdir, &sbuf) != 0) {
rfbLog("creating dir: %s\n", cdir);
if (mkdir(cdir, 0755) != 0) {
rfbLog("could not create directory %s\n", cdir);
rfbLogPerror("mkdir");
return NULL;
}
} else if (! S_ISDIR(sbuf.st_mode)) {
rfbLog("not a directory: %s\n", cdir);
return NULL;
}
}
sprintf(cdir, "%s%s", home, cases1[1]);
}
tmp = (char *) malloc(strlen(cdir) + 10);
for (i=0; i<3; i++) {
int ret;
sprintf(tmp, "%s%s", cdir, cases2[i]);
if (stat(tmp, &sbuf) != 0) {
rfbLog("creating dir: %s\n", tmp);
if (! strcmp(cases2[i], "/tmp")) {
ret = mkdir(tmp, 0700);
} else {
ret = mkdir(tmp, 0755);
}
if (ret != 0) {
rfbLog("could not create directory %s\n", tmp);
rfbLogPerror("mkdir");
return NULL;
}
} else if (! S_ISDIR(sbuf.st_mode)) {
rfbLog("not a directory: %s\n", tmp);
return NULL;
}
}
sprintf(tmp, "%s/tmp", cdir);
*tmp_in = tmp;
return cdir;
}
void sslGenCA(char *cdir) {
char *openssl = find_openssl_bin();
char *tmp, *cmd, *scr, *cdir_use;
FILE *out;
if (! openssl) {
exit(1);
}
cdir_use = get_Cert_dir(cdir, &tmp);
if (! cdir_use) {
exit(1);
}
cmd = (char *) malloc(strlen(tmp) + 100);
scr = (char *) malloc(strlen(tmp) + 100);
sprintf(cmd, "%s/genca.%d.sh", tmp, getpid());
out = fopen(cmd, "w");
if (! out) {
rfbLog("could not open: %s\n", cmd);
rfbLogPerror("fopen");
exit(1);
}
fprintf(out, "%s", genCA);
fclose(out);
sprintf(scr, "/bin/sh %s", cmd);
rfbLog("Using openssl: %s\n", openssl);
rfbLog("Using certs dir: %s\n", cdir_use);
fprintf(stderr, "\n");
set_env("BASE_DIR", cdir_use);
set_env("OPENSSL", openssl);
system(scr);
unlink(cmd);
}
void sslGenCert(char *ty, char *nm) {
char *openssl = find_openssl_bin();
char *tmp, *cmd, *scr, *cdir_use;
FILE *out;
if (! openssl) {
exit(1);
}
cdir_use = get_Cert_dir(NULL, &tmp);
if (! cdir_use) {
exit(1);
}
cmd = (char *) malloc(strlen(tmp) + 100);
scr = (char *) malloc(strlen(tmp) + 100);
sprintf(cmd, "%s/gencert.%d.sh", tmp, getpid());
out = fopen(cmd, "w");
if (! out) {
rfbLog("could not open: %s\n", cmd);
rfbLogPerror("fopen");
exit(1);
}
fprintf(out, "%s", genCert);
fclose(out);
sprintf(scr, "/bin/sh %s", cmd);
rfbLog("Using openssl: %s\n", openssl);
rfbLog("Using certs dir: %s\n", cdir_use);
fprintf(stderr, "\n");
set_env("BASE_DIR", cdir_use);
set_env("OPENSSL", openssl);
if (! ty) {
set_env("TYPE", "");
} else {
set_env("TYPE", ty);
}
if (! nm) {
set_env("NAME", "");
} else {
char *q = strstr(nm, "SAVE-");
if (!strcmp(nm, "SAVE")) {
set_env("NAME", "");
} else if (q == nm) {
q += strlen("SAVE-");
set_env("NAME", q);
} else {
set_env("NAME", nm);
}
}
system(scr);
unlink(cmd);
}
void sslEncKey(char *path, int mode) {
char *openssl = find_openssl_bin();
char *scr, *cert = NULL, *tca;
char line[1024], tmp[] = "/tmp/x11vnc-tmp.XXXXXX";
char *cdir = NULL;
int tmp_fd, incert, info_only = 0, delete_only = 0;
int listlong = 0;
struct stat sbuf;
FILE *file;
static int depth = 0;
if (depth > 0) {
/* get_saved_pem may call us back. */
return;
}
depth++;
if (mode == 1) {
info_only = 1;
} else if (mode == 2) {
delete_only = 1;
}
if (! openssl) {
exit(1);
}
cdir = get_Cert_dir(NULL, &tca);
if (! cdir) {
fprintf(stderr, "could not find Cert dir\n");
exit(1);
}
if (!strcasecmp(path, "LL") || !strcasecmp(path, "LISTL")) {
listlong = 1;
path = "LIST";
}
if (strstr(path, "SAVE") == path) {
char *p = get_saved_pem(path, 0);
if (p == NULL) {
fprintf(stderr, "could not find saved pem matching: %s\n", path);
exit(1);
}
path = p;
} else if (!strcmp(path, "CA") && cdir) {
tca = (char *) malloc(strlen(cdir) + strlen("/CA/cacert.pem") + 1);
sprintf(tca, "%s/CA/cacert.pem", cdir);
path = tca;
} else if (info_only && (!strcasecmp(path, "LIST") || !strcasecmp(path, "LS") ||
!strcasecmp(path, "ALL"))) {
if (! cdir || strchr(cdir, '\'')) {
fprintf(stderr, "bad certdir char: %s\n", cdir ? cdir : "null");
exit(1);
}
tca = (char *) malloc(2*strlen(cdir) + strlen(program_name) + 1000);
sprintf(tca, "find '%s' | egrep '/(CA|tmp|clients)$|\\.(crt|pem|key|req)$' "
"| grep -v CA/newcerts", cdir);
if (!strcasecmp(path, "ALL")) {
/* ugh.. */
strcat(tca, " | egrep -v 'private/cakey.pem|(CA|tmp|clients)$' | xargs -n1 ");
strcat(tca, program_name);
strcat(tca, " -ssldir '");
strcat(tca, cdir);
strcat(tca, "' -sslCertInfo 2>&1 ");
} else if (listlong) {
strcat(tca, " | xargs ls -ld ");
}
system(tca);
return;
} else if (info_only && (!strcasecmp(path, "HASHON")
|| !strcasecmp(path, "HASHOFF"))) {
tmp_fd = mkstemp(tmp);
if (tmp_fd < 0) {
exit(1);
}
write(tmp_fd, genCert, strlen(genCert));
close(tmp_fd);
scr = (char *) malloc(strlen(tmp) + 100);
sprintf(scr, "/bin/sh %s", tmp);
set_env("BASE_DIR", cdir);
set_env("OPENSSL", openssl);
set_env("TYPE", "server");
if (!strcasecmp(path, "HASHON")) {
set_env("HASHON", "1");
} else {
set_env("HASHOFF", "1");
}
system(scr);
unlink(tmp);
return;
}
if (stat(path, &sbuf) != 0) {
if (strstr(path, "client") || strchr(path, '/') == NULL) {
int i;
tca = (char *) malloc(strlen(cdir) + strlen("/clients")
+ strlen(path) + 100);
for (i = 1; i <= 15; i++) {
tca[0] = '\0';
if ( i == 1) {
sprintf(tca, "%s/%s", cdir, path);
} else if (i == 2 && mode > 0) {
sprintf(tca, "%s/%s.crt", cdir, path);
} else if (i == 3) {
sprintf(tca, "%s/%s.pem", cdir, path);
} else if (i == 4 && mode > 1) {
sprintf(tca, "%s/%s.req", cdir, path);
} else if (i == 5 && mode > 1) {
sprintf(tca, "%s/%s.key", cdir, path);
} else if (i == 6) {
sprintf(tca, "%s/clients/%s", cdir, path);
} else if (i == 7 && mode > 0) {
sprintf(tca, "%s/clients/%s.crt", cdir, path);
} else if (i == 8) {
sprintf(tca, "%s/clients/%s.pem", cdir, path);
} else if (i == 9 && mode > 1) {
sprintf(tca, "%s/clients/%s.req", cdir, path);
} else if (i == 10 && mode > 1) {
sprintf(tca, "%s/clients/%s.key", cdir, path);
} else if (i == 11) {
sprintf(tca, "%s/server-%s", cdir, path);
} else if (i == 12 && mode > 0) {
sprintf(tca, "%s/server-%s.crt", cdir, path);
} else if (i == 13) {
sprintf(tca, "%s/server-%s.pem", cdir, path);
} else if (i == 14 && mode > 1) {
sprintf(tca, "%s/server-%s.req", cdir, path);
} else if (i == 15 && mode > 1) {
sprintf(tca, "%s/server-%s.key", cdir, path);
}
if (tca[0] == '\0') {
continue;
}
if (stat(tca, &sbuf) == 0) {
path = tca;
break;
}
}
}
}
if (stat(path, &sbuf) != 0) {
rfbLog("sslEncKey: %s\n", path);
rfbLogPerror("stat");
exit(1);
}
if (! info_only) {
cert = (char *) malloc(2*sbuf.st_size);
file = fopen(path, "r");
if (file == NULL) {
rfbLog("sslEncKey: %s\n", path);
rfbLogPerror("fopen");
exit(1);
}
incert = 0;
cert[0] = '\0';
while (fgets(line, 1024, file) != NULL) {
if (strstr(line, "-----BEGIN CERTIFICATE-----") == line) {
incert = 1;
}
if (incert) {
strcat(cert, line);
}
if (strstr(line, "-----END CERTIFICATE-----") == line) {
incert = 0;
}
}
fclose(file);
}
tmp_fd = mkstemp(tmp);
if (tmp_fd < 0) {
exit(1);
}
write(tmp_fd, genCert, strlen(genCert));
close(tmp_fd);
scr = (char *) malloc(strlen(tmp) + 100);
sprintf(scr, "/bin/sh %s", tmp);
set_env("BASE_DIR", "/no/such/dir");
set_env("OPENSSL", openssl);
set_env("TYPE", "server");
if (info_only) {
set_env("INFO_ONLY", path);
} else if (delete_only) {
set_env("DELETE_ONLY", path);
} else {
set_env("ENCRYPT_ONLY", path);
}
system(scr);
unlink(tmp);
if (! mode && cert && cert[0] != '\0') {
file = fopen(path, "a");
if (file == NULL) {
rfbLog("sslEncKey: %s\n", path);
rfbLogPerror("fopen");
exit(1);
}
fprintf(file, cert);
fclose(file);
free(cert);
}
depth--;
}