package main import ( "encoding/json" "fmt" "io" "math/rand" "net/http" "regexp" "strconv" "time" "imuslab.com/zoraxy/mod/acme" "imuslab.com/zoraxy/mod/dynamicproxy" "imuslab.com/zoraxy/mod/utils" ) /* acme.go This script handle special routing required for acme auto cert renew functions */ // Helper function to generate a random port above a specified value func getRandomPort(minPort int) int { return rand.Intn(65535-minPort) + minPort } // init the new ACME instance func initACME() *acme.ACMEHandler { SystemWideLogger.Println("Starting ACME handler") rand.Seed(time.Now().UnixNano()) // Generate a random port above 30000 port := getRandomPort(30000) // Check if the port is already in use for acme.IsPortInUse(port) { port = getRandomPort(30000) } return acme.NewACME("https://acme-v02.api.letsencrypt.org/directory", strconv.Itoa(port), sysdb, SystemWideLogger) } // Restart ACME handler and auto renewer func restartACMEHandler() { SystemWideLogger.Println("Restarting ACME handler") //Clos the current handler and auto renewer acmeHandler.Close() acmeAutoRenewer.Close() acmeDeregisterSpecialRoutingRule() //Reinit the handler with a new random port acmeHandler = initACME() acmeRegisterSpecialRoutingRule() } // create the special routing rule for ACME func acmeRegisterSpecialRoutingRule() { SystemWideLogger.Println("Assigned temporary port:" + acmeHandler.Getport()) err := dynamicProxyRouter.AddRoutingRules(&dynamicproxy.RoutingRule{ ID: "acme-autorenew", MatchRule: func(r *http.Request) bool { found, _ := regexp.MatchString("/.well-known/acme-challenge/*", r.RequestURI) return found }, RoutingHandler: func(w http.ResponseWriter, r *http.Request) { req, err := http.NewRequest(http.MethodGet, "http://localhost:"+acmeHandler.Getport()+r.RequestURI, nil) req.Host = r.Host if err != nil { fmt.Printf("client: could not create request: %s\n", err) return } res, err := http.DefaultClient.Do(req) if err != nil { fmt.Printf("client: error making http request: %s\n", err) return } resBody, err := io.ReadAll(res.Body) defer res.Body.Close() if err != nil { fmt.Printf("error reading: %s\n", err) return } w.Write(resBody) }, Enabled: true, UseSystemAccessControl: false, }) if err != nil { SystemWideLogger.PrintAndLog("ACME", "Unable register temp port for DNS resolver", err) } } // remove the special routing rule for ACME func acmeDeregisterSpecialRoutingRule() { SystemWideLogger.Println("Removing ACME routing rule") dynamicProxyRouter.RemoveRoutingRule("acme-autorenew") } // This function check if the renew setup is satisfied. If not, toggle them automatically func AcmeCheckAndHandleRenewCertificate(w http.ResponseWriter, r *http.Request) { isForceHttpsRedirectEnabledOriginally := false requireRestorePort80 := false dnsPara, _ := utils.PostBool(r, "dns") if !dnsPara { if dynamicProxyRouter.Option.Port == 443 { //Check if port 80 is enabled if !dynamicProxyRouter.Option.ListenOnPort80 { //Enable port 80 temporarily SystemWideLogger.PrintAndLog("ACME", "Temporarily enabling port 80 listener to handle ACME request ", nil) dynamicProxyRouter.UpdatePort80ListenerState(true) requireRestorePort80 = true time.Sleep(2 * time.Second) } //Enable port 80 to 443 redirect if !dynamicProxyRouter.Option.ForceHttpsRedirect { SystemWideLogger.Println("Temporary enabling HTTP to HTTPS redirect for ACME certificate renew requests") dynamicProxyRouter.UpdateHttpToHttpsRedirectSetting(true) } else { //Set this to true, so after renew, do not turn it off isForceHttpsRedirectEnabledOriginally = true } } else if dynamicProxyRouter.Option.Port == 80 { //Go ahead } else { //This port do not support ACME utils.SendErrorResponse(w, "ACME renew only support web server listening on port 80 (http) or 443 (https)") return } } //Add a 2 second delay to make sure everything is settle down time.Sleep(2 * time.Second) // Pass over to the acmeHandler to deal with the communication acmeHandler.HandleRenewCertificate(w, r) //Update the TLS cert store buffer tlsCertManager.UpdateLoadedCertList() //Restore original settings if requireRestorePort80 { //Restore port 80 listener SystemWideLogger.PrintAndLog("ACME", "Restoring previous port 80 listener settings", nil) dynamicProxyRouter.UpdatePort80ListenerState(false) } if !isForceHttpsRedirectEnabledOriginally { //Default is off. Turn the redirection off SystemWideLogger.PrintAndLog("ACME", "Restoring HTTP to HTTPS redirect settings", nil) dynamicProxyRouter.UpdateHttpToHttpsRedirectSetting(false) } } // HandleACMEPreferredCA return the user preferred / default CA for new subdomain auto creation func HandleACMEPreferredCA(w http.ResponseWriter, r *http.Request) { ca, err := utils.PostPara(r, "set") if err != nil { //Return the current ca to user prefCA := "Let's Encrypt" sysdb.Read("acmepref", "prefca", &prefCA) js, _ := json.Marshal(prefCA) utils.SendJSONResponse(w, string(js)) } else { //Check if the CA is supported acme.IsSupportedCA(ca) //Set the new config sysdb.Write("acmepref", "prefca", ca) SystemWideLogger.Println("Updating prefered ACME CA to " + ca) utils.SendOK(w) } }