repo: tlswrap
action: commit
revision: 
path_from: 
revision_from: cc004d0bef4111be96f779f8ee768fee57dd887d:
path_to: 
revision_to: 
git.thebackupbox.net
tlswrap
git clone git://git.thebackupbox.net/tlswrap
commit cc004d0bef4111be96f779f8ee768fee57dd887d
Author: epoch 
Date:   Mon Apr 8 08:46:42 2024 +0000

    added the ability to pick per-servername certs based on SNI.

diff --git a/tlswrap.c b/tlswrap.c
index 65e29782af24a1d3d133a6a4bc93a9c483fef6d1..
index ..8063100d75d705543529dc56ab53cd8097c8438a 100644
--- a/tlswrap.c
+++ b/tlswrap.c
@@ -2,6 +2,7 @@
 #include 
 #include 
 #include 
+#include 

 #include 
 #include  // waitpid
@@ -147,10 +148,55 @@ int client_cert(const SSL *ssl) {
   return 1;
 }

+#include 
+
+// returns 1 on success.
+int cert_cb(SSL *ssl, void *arg) {
+  servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
+  if(!servername) {
+    return 1;// no servername, let's just use whatever we had already.
+  }
+  if(chdir("/etc/tlswrap/") != 0) {
+    return 1;// if no extra certs are configured, no worries.
+  }
+
+  // I think I am going to open the dir for reading, then compare the servername to all of the dirs in there using fnmatch.
+  // configuration can just be done with a subdir named *.stuff for wildcard domains.
+  syslog(LOG_DAEMON|LOG_ERR,"servername: %s",servername);
+  if(strstr(servername,"..")) { // is there any good reason for this?
+    syslog(LOG_DAEMON|LOG_ERR,"someone tried to directory traversal the servername");
+    return 0;//might as well make it error here.
+  }
+  DIR *dir=opendir(".");
+  struct dirent *dent;
+  while(dent=readdir(dir)) {
+    if(!strcmp(dent->d_name,".") || !strcmp(dent->d_name,"..")) continue;
+    syslog(LOG_DAEMON|LOG_ERR,"testing domain %s against %s",dent->d_name,servername);
+    if(!fnmatch(dent->d_name,servername,FNM_NOESCAPE)) {
+      syslog(LOG_DAEMON|LOG_ERR,"FOUND THE MATCHING DOMAIN");
+      if(chdir(dent->d_name) == 0) {
+        break; // we found a configured domain.
+      } else {
+        syslog(LOG_DAEMON|LOG_ERR,"failed to chdir to %s",servername);
+      }
+    }
+  }
+  closedir(dir);
+  if(SSL_use_certificate_chain_file(ssl, "cert") <= 0) {
+    syslog(LOG_DAEMON|LOG_ERR,"failed to load servername cert %s %s",getcwd(0,200),strerror(errno));
+    return 1;// even if we can't find the cert, no worries. we got sane fallback.
+  }
+  if(SSL_use_PrivateKey_file(ssl, "key", SSL_FILETYPE_PEM) <= 0) {
+    syslog(LOG_DAEMON|LOG_ERR,"failed to load servername key %s %s",getcwd(0,200),strerror(errno));
+    return 0;// if we loaded the cert but can't find the key. worries. key and cert gotta match.
+  }
+  return 1;
+}
+
 //what is ad and arg?
 int sni_cb(SSL *ssl, int *ad, void *arg) {
   if(!ssl) return SSL_TLSEXT_ERR_NOACK;
-  //SSL_CTX *ctx=SSL_get_SSL_CTX(ssl);
+//  SSL_CTX *ctx=SSL_get_SSL_CTX(ssl);
   servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
   if(!servername || servername[0] == '\0') {
     syslog(LOG_DAEMON|LOG_DEBUG,"no SNI");
@@ -162,23 +208,6 @@ int sni_cb(SSL *ssl, int *ad, void *arg) {
   }
   syslog(LOG_DAEMON|LOG_DEBUG,"SNI: %s",servername);
   return SSL_TLSEXT_ERR_OK;
-  //TODO: figure out a good way to do certs based on vhost here.
-  if(chdir("/etc/ssl/certs/") != 0) {
-    return SSL_TLSEXT_ERR_OK;//skipping per-vhost certs and keys
-  }
-  //if(SSL_CTX_use_certificate_chain_file(ctx, servername) <= 0) {
-  //  syslog(LOG_DAEMON|LOG_ERR,"failed to load servername cert");
-  //  return SSL_TLSEXT_ERR_NOACK;
- // }
-  if(chdir("/etc/ssl/keys/") != 0) {
-    return SSL_TLSEXT_ERR_OK;//not sure if returning here will break stuff or not
-  }
-  //if(SSL_CTX_use_PrivateKey_file(ctx, servername, SSL_FILETYPE_PEM) <= 0) {
-  //  syslog(LOG_DAEMON|LOG_ERR,"failed to load servername key");
-  //  return SSL_TLSEXT_ERR_NOACK;
- // }
-  //probably attempt to open certs named after the vhosts in a config dir.
-  return SSL_TLSEXT_ERR_OK;
 }

 int verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx) {
@@ -398,7 +427,6 @@ int tlswrap_SSL_early_cb_fn(SSL *s, int *al, void *arg) {
 #endif

 int main(int argc,char *argv[]) {
-  setpgid(0,0);
   setvbuf(stdin, NULL, _IONBF, 0);
   setvbuf(stdout, NULL, _IONBF, 0);
   setvbuf(stderr, NULL, _IONBF, 0);
@@ -511,6 +539,7 @@ int main(int argc,char *argv[]) {
   }

   SSL_CTX_set_tlsext_servername_callback(ctx,sni_cb);
+  SSL_CTX_set_cert_cb(ctx,cert_cb,NULL);
   ssl = SSL_new(ctx);

 #ifdef JA3
@@ -580,6 +609,7 @@ int main(int argc,char *argv[]) {

   int child=fork();
   if(child == 0) {
+    setpgid(0,0);
     x=dup(0);
     dup2(a[0],0);
     dup2(b[1],1);
@@ -684,28 +714,32 @@ int main(int argc,char *argv[]) {
       }
     }
   }
+#ifdef NOSHUTDOWN
+  exit(0);
+#endif
   SSL_shutdown(ssl);
   SSL_free(ssl);
   EVP_cleanup();

   int status;
   char *url=getenv("REMOTE_URL");
-  if(killpg(getpid(),SIGCONT)) { //if the process hasn't exited already, let's tell them they've been hungup on.
+  if(kill(-child,SIGCONT)) { //if the process hasn't exited already, let's tell them they've been hungup on.
     syslog(LOG_DAEMON|LOG_WARNING,"%s: killpg(%d,SIGCONT): %s",url,getpid(),strerror(errno));
   }
   sleep(5);
   if(waitpid(child,&status,WNOHANG) == child) {
-    syslog(LOG_DAEMON|LOG_ERR,"%s: child process exited after a SIGCONT and a 5s wait. gonna send a HUP to everything else anyway.",url);
+    syslog(LOG_DAEMON|LOG_DEBUG,"%s: child process exited after a SIGCONT and a 5s wait. gonna send a HUP to everything else anyway.",url);
+    // this is fine. no need to ERR it.
   }
-  if(killpg(getpid(),SIGHUP)) {
+  if(kill(-child,SIGHUP)) {
     syslog(LOG_DAEMON|LOG_ERR,"%s: killpg: %s",url,strerror(errno));
   }
   sleep(5);
-  if(killpg(getpid(),SIGTERM)) {
+  if(kill(-child,SIGTERM)) {
     syslog(LOG_DAEMON|LOG_ERR,"%s: killpg: %s",url,strerror(errno));
   }
   sleep(5);
-  if(killpg(getpid(),SIGKILL)) {
+  if(kill(-child,SIGKILL)) {
     syslog(LOG_DAEMON|LOG_ERR,"%s: killpg: %s",url,strerror(errno));
   }
   syslog(LOG_DAEMON|LOG_ERR,"%s: sent signals to all processes in our process group. they should be DEAD",url);

-----END OF PAGE-----