mirror of
				https://github.com/therootcompany/greenlock-express.js.git
				synced 2024-11-16 17:28:59 +00:00 
			
		
		
		
	wip: API looks good, on to testing
This commit is contained in:
		
							parent
							
								
									9ab7844ea8
								
							
						
					
					
						commit
						0dd3641dc2
					
				
							
								
								
									
										30
									
								
								demo.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								demo.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,30 @@ | ||||
| "use strict"; | ||||
| 
 | ||||
| var Greenlock = require("./"); | ||||
| var greenlockOptions = { | ||||
| 	cluster: false, | ||||
| 
 | ||||
| 	maintainerEmail: "greenlock-test@rootprojects.org", | ||||
| 	servername: "foo-gl.test.utahrust.com", | ||||
| 	serverId: "bowie.local" | ||||
| 
 | ||||
| 	/* | ||||
|   manager: { | ||||
|     module: "greenlock-manager-sequelize", | ||||
|     dbUrl: "postgres://foo@bar:baz/quux" | ||||
|   } | ||||
|   */ | ||||
| }; | ||||
| 
 | ||||
| Greenlock.create(greenlockOptions) | ||||
| 	.worker(function(glx) { | ||||
| 		console.info(); | ||||
| 		console.info("Hello from worker"); | ||||
| 
 | ||||
| 		glx.serveApp(function(req, res) { | ||||
| 			res.end("Hello, Encrypted World!"); | ||||
| 		}); | ||||
| 	}) | ||||
| 	.master(function() { | ||||
| 		console.log("Hello from master"); | ||||
| 	}); | ||||
							
								
								
									
										29
									
								
								greenlock-express.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								greenlock-express.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,29 @@ | ||||
| "use strict"; | ||||
| 
 | ||||
| require("./lib/compat"); | ||||
| 
 | ||||
| // Greenlock Express
 | ||||
| var GLE = module.exports; | ||||
| 
 | ||||
| // opts.approveDomains(options, certs, cb)
 | ||||
| GLE.create = function(opts) { | ||||
| 	if (!opts) { | ||||
| 		opts = {}; | ||||
| 	} | ||||
| 
 | ||||
| 	// just for ironic humor
 | ||||
| 	["cloudnative", "cloudscale", "webscale", "distributed", "blockchain"].forEach(function(k) { | ||||
| 		if (opts[k]) { | ||||
| 			opts.cluster = true; | ||||
| 		} | ||||
| 	}); | ||||
| 
 | ||||
| 	// we want to be minimal, and only load the code that's necessary to load
 | ||||
| 	if (opts.cluster) { | ||||
| 		if (require("cluster").isMaster) { | ||||
| 			return require("./master.js").create(opts); | ||||
| 		} | ||||
| 		return require("./worker.js").create(opts); | ||||
| 	} | ||||
| 	return require("./single.js").create(opts); | ||||
| }; | ||||
							
								
								
									
										102
									
								
								http-middleware.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								http-middleware.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,102 @@ | ||||
| "use strict"; | ||||
| 
 | ||||
| var HttpMiddleware = module.exports; | ||||
| var servernameRe = /^[a-z0-9\.\-]+$/i; | ||||
| var challengePrefix = "/.well-known/acme-challenge/"; | ||||
| 
 | ||||
| HttpMiddleware.create = function(gl, defaultApp) { | ||||
| 	if (defaultApp && "function" !== typeof defaultApp) { | ||||
| 		throw new Error("use greenlock.httpMiddleware() or greenlock.httpMiddleware(function (req, res) {})"); | ||||
| 	} | ||||
| 
 | ||||
| 	return function(req, res, next) { | ||||
| 		var hostname = HttpMiddleware.sanitizeHostname(req); | ||||
| 
 | ||||
| 		req.on("error", function(err) { | ||||
| 			explainError(gl, err, "http_01_middleware_socket", hostname); | ||||
| 		}); | ||||
| 
 | ||||
| 		if (skipIfNeedBe(req, res, next, defaultApp, hostname)) { | ||||
| 			return; | ||||
| 		} | ||||
| 
 | ||||
| 		var token = req.url.slice(challengePrefix.length); | ||||
| 
 | ||||
| 		gl.getAcmeHttp01ChallengeResponse({ type: "http-01", servername: hostname, token: token }) | ||||
| 			.then(function(result) { | ||||
| 				respondWithGrace(res, result, hostname, token); | ||||
| 			}) | ||||
| 			.catch(function(err) { | ||||
| 				respondToError(gl, res, err, "http_01_middleware_challenge_response", hostname); | ||||
| 			}); | ||||
| 	}; | ||||
| }; | ||||
| 
 | ||||
| function skipIfNeedBe(req, res, next, defaultApp, hostname) { | ||||
| 	if (!hostname || 0 !== req.url.indexOf(challengePrefix)) { | ||||
| 		if ("function" === typeof defaultApp) { | ||||
| 			defaultApp(req, res, next); | ||||
| 		} else if ("function" === typeof next) { | ||||
| 			next(); | ||||
| 		} else { | ||||
| 			res.statusCode = 500; | ||||
| 			res.end("[500] Developer Error: app.use('/', greenlock.httpMiddleware()) or greenlock.httpMiddleware(app)"); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| function respondWithGrace(res, result, hostname, token) { | ||||
| 	var keyAuth = result.keyAuthorization; | ||||
| 	if (keyAuth && "string" === typeof keyAuth) { | ||||
| 		res.setHeader("Content-Type", "text/plain; charset=utf-8"); | ||||
| 		res.end(keyAuth); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	res.statusCode = 404; | ||||
| 	res.setHeader("Content-Type", "application/json; charset=utf-8"); | ||||
| 	res.end(JSON.stringify({ error: { message: "domain '" + hostname + "' has no token '" + token + "'." } })); | ||||
| } | ||||
| 
 | ||||
| function explainError(gl, err, ctx, hostname) { | ||||
| 	if (!err.servername) { | ||||
| 		err.servername = hostname; | ||||
| 	} | ||||
| 	if (!err.context) { | ||||
| 		err.context = ctx; | ||||
| 	} | ||||
| 	(gl.notify || gl._notify)("error", err); | ||||
| 	return err; | ||||
| } | ||||
| 
 | ||||
| function respondToError(gl, res, err, ctx, hostname) { | ||||
| 	err = explainError(gl, err, ctx, hostname); | ||||
| 	res.statusCode = 500; | ||||
| 	res.end("Internal Server Error: See logs for details."); | ||||
| } | ||||
| 
 | ||||
| HttpMiddleware.getHostname = function(req) { | ||||
| 	return req.hostname || req.headers["x-forwarded-host"] || (req.headers.host || ""); | ||||
| }; | ||||
| HttpMiddleware.sanitizeHostname = function(req) { | ||||
| 	// we can trust XFH because spoofing causes no ham in this limited use-case scenario
 | ||||
| 	// (and only telebit would be legitimately setting XFH)
 | ||||
| 	var servername = HttpMiddleware.getHostname(req) | ||||
| 		.toLowerCase() | ||||
| 		.replace(/:.*/, ""); | ||||
| 	try { | ||||
| 		req.hostname = servername; | ||||
| 	} catch (e) { | ||||
| 		// read-only express property
 | ||||
| 	} | ||||
| 	if (req.headers["x-forwarded-host"]) { | ||||
| 		req.headers["x-forwarded-host"] = servername; | ||||
| 	} | ||||
| 	try { | ||||
| 		req.headers.host = servername; | ||||
| 	} catch (e) { | ||||
| 		// TODO is this a possible error?
 | ||||
| 	} | ||||
| 
 | ||||
| 	return (servernameRe.test(servername) && -1 === servername.indexOf("..") && servername) || ""; | ||||
| }; | ||||
							
								
								
									
										133
									
								
								https-middleware.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								https-middleware.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,133 @@ | ||||
| "use strict"; | ||||
| 
 | ||||
| var SanitizeHost = module.exports; | ||||
| var HttpMiddleware = require("./http-middleware.js"); | ||||
| 
 | ||||
| SanitizeHost.create = function(gl, app) { | ||||
| 	return function(req, res, next) { | ||||
| 		function realNext() { | ||||
| 			if ("function" === typeof app) { | ||||
| 				app(req, res); | ||||
| 			} else if ("function" === typeof next) { | ||||
| 				next(); | ||||
| 			} else { | ||||
| 				res.statusCode = 500; | ||||
| 				res.end("Error: no middleware assigned"); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		var hostname = HttpMiddleware.getHostname(req); | ||||
| 		// Replace the hostname, and get the safe version
 | ||||
| 		var safehost = HttpMiddleware.sanitizeHostname(req); | ||||
| 
 | ||||
| 		// if no hostname, move along
 | ||||
| 		if (!hostname) { | ||||
| 			realNext(); | ||||
| 			return; | ||||
| 		} | ||||
| 
 | ||||
| 		// if there were unallowed characters, complain
 | ||||
| 		if (safehost.length !== hostname.length) { | ||||
| 			res.statusCode = 400; | ||||
| 			res.end("Malformed HTTP Header: 'Host: " + hostname + "'"); | ||||
| 			return; | ||||
| 		} | ||||
| 
 | ||||
| 		// Note: This sanitize function is also called on plain sockets, which don't need Domain Fronting checks
 | ||||
| 		if (req.socket.encrypted) { | ||||
| 			if (req.socket && "string" === typeof req.socket.servername) { | ||||
| 				// Workaround for https://github.com/nodejs/node/issues/22389
 | ||||
| 				if (!SanitizeHost._checkServername(safehost, req.socket)) { | ||||
| 					res.statusCode = 400; | ||||
| 					res.setHeader("Content-Type", "text/html; charset=utf-8"); | ||||
| 					res.end( | ||||
| 						"<h1>Domain Fronting Error</h1>" + | ||||
| 							"<p>This connection was secured using TLS/SSL for '" + | ||||
| 							(req.socket.servername || "").toLowerCase() + | ||||
| 							"'</p>" + | ||||
| 							"<p>The HTTP request specified 'Host: " + | ||||
| 							safehost + | ||||
| 							"', which is (obviously) different.</p>" + | ||||
| 							"<p>Because this looks like a domain fronting attack, the connection has been terminated.</p>" | ||||
| 					); | ||||
| 					return; | ||||
| 				} | ||||
| 			} | ||||
| 			/* | ||||
|       else if (safehost && !gl._skip_fronting_check) { | ||||
| 
 | ||||
| 				// We used to print a log message here, but it turns out that it's
 | ||||
| 				// really common for IoT devices to not use SNI (as well as many bots
 | ||||
| 				// and such).
 | ||||
| 				// It was common for the log message to pop up as the first request
 | ||||
| 				// to the server, and that was confusing. So instead now we do nothing.
 | ||||
| 
 | ||||
| 				//console.warn("no string for req.socket.servername," + " skipping fronting check for '" + safehost + "'");
 | ||||
| 				//gl._skip_fronting_check = true;
 | ||||
| 			} | ||||
|       */ | ||||
| 		} | ||||
| 
 | ||||
| 		// carry on
 | ||||
| 		realNext(); | ||||
| 	}; | ||||
| }; | ||||
| 
 | ||||
| var warnDomainFronting = true; | ||||
| var warnUnexpectedError = true; | ||||
| SanitizeHost._checkServername = function(safeHost, tlsSocket) { | ||||
| 	var servername = (tlsSocket.servername || "").toLowerCase(); | ||||
| 
 | ||||
| 	// acceptable: older IoT devices may lack SNI support
 | ||||
| 	if (!servername) { | ||||
| 		return true; | ||||
| 	} | ||||
| 	// acceptable: odd... but acceptable
 | ||||
| 	if (!safeHost) { | ||||
| 		return true; | ||||
| 	} | ||||
| 	if (safeHost === servername) { | ||||
| 		return true; | ||||
| 	} | ||||
| 
 | ||||
| 	if ("function" !== typeof tlsSocket.getCertificate) { | ||||
| 		// domain fronting attacks allowed
 | ||||
| 		if (warnDomainFronting) { | ||||
| 			// https://github.com/nodejs/node/issues/24095
 | ||||
| 			console.warn( | ||||
| 				"Warning: node " + | ||||
| 					process.version + | ||||
| 					" is vulnerable to domain fronting attacks. Please use node v11.2.0 or greater." | ||||
| 			); | ||||
| 			warnDomainFronting = false; | ||||
| 		} | ||||
| 		return true; | ||||
| 	} | ||||
| 
 | ||||
| 	// connection established with servername and session is re-used for allowed name
 | ||||
| 	// See https://github.com/nodejs/node/issues/24095
 | ||||
| 	var cert = tlsSocket.getCertificate(); | ||||
| 	try { | ||||
| 		// TODO optimize / cache?
 | ||||
| 		// *should* always have a string, right?
 | ||||
| 		// *should* always be lowercase already, right?
 | ||||
| 		if ( | ||||
| 			(cert.subject.CN || "").toLowerCase() !== safeHost && | ||||
| 			!(cert.subjectaltname || "").split(/,\s+/).some(function(name) { | ||||
| 				// always prefixed with "DNS:"
 | ||||
| 				return safeHost === name.slice(4).toLowerCase(); | ||||
| 			}) | ||||
| 		) { | ||||
| 			return false; | ||||
| 		} | ||||
| 	} catch (e) { | ||||
| 		// not sure what else to do in this situation...
 | ||||
| 		if (warnUnexpectedError) { | ||||
| 			console.warn("Warning: encoutered error while performing domain fronting check: " + e.message); | ||||
| 			warnUnexpectedError = false; | ||||
| 		} | ||||
| 		return true; | ||||
| 	} | ||||
| 
 | ||||
| 	return false; | ||||
| }; | ||||
							
								
								
									
										334
									
								
								index.js
									
									
									
									
									
								
							
							
						
						
									
										334
									
								
								index.js
									
									
									
									
									
								
							| @ -1,334 +0,0 @@ | ||||
| "use strict"; | ||||
| 
 | ||||
| var PromiseA; | ||||
| try { | ||||
| 	PromiseA = require("bluebird"); | ||||
| } catch (e) { | ||||
| 	PromiseA = global.Promise; | ||||
| } | ||||
| 
 | ||||
| // opts.approveDomains(options, certs, cb)
 | ||||
| module.exports.create = function(opts) { | ||||
| 	// accept all defaults for greenlock.challenges, greenlock.store, greenlock.middleware
 | ||||
| 	if (!opts._communityPackage) { | ||||
| 		opts._communityPackage = "greenlock-express.js"; | ||||
| 		opts._communityPackageVersion = require("./package.json").version; | ||||
| 	} | ||||
| 
 | ||||
| 	function explainError(e) { | ||||
| 		console.error("Error:" + e.message); | ||||
| 		if ("EACCES" === e.errno) { | ||||
| 			console.error("You don't have prmission to access '" + e.address + ":" + e.port + "'."); | ||||
| 			console.error('You probably need to use "sudo" or "sudo setcap \'cap_net_bind_service=+ep\' $(which node)"'); | ||||
| 			return; | ||||
| 		} | ||||
| 		if ("EADDRINUSE" === e.errno) { | ||||
| 			console.error("'" + e.address + ":" + e.port + "' is already being used by some other program."); | ||||
| 			console.error("You probably need to stop that program or restart your computer."); | ||||
| 			return; | ||||
| 		} | ||||
| 		console.error(e.code + ": '" + e.address + ":" + e.port + "'"); | ||||
| 	} | ||||
| 
 | ||||
| 	function _createPlain(plainPort) { | ||||
| 		if (!plainPort) { | ||||
| 			plainPort = 80; | ||||
| 		} | ||||
| 
 | ||||
| 		var parts = String(plainPort).split(":"); | ||||
| 		var p = parts.pop(); | ||||
| 		var addr = parts | ||||
| 			.join(":") | ||||
| 			.replace(/^\[/, "") | ||||
| 			.replace(/\]$/, ""); | ||||
| 		var args = []; | ||||
| 		var httpType; | ||||
| 		var server; | ||||
| 		var validHttpPort = parseInt(p, 10) >= 0; | ||||
| 
 | ||||
| 		if (addr) { | ||||
| 			args[1] = addr; | ||||
| 		} | ||||
| 		if (!validHttpPort && !/(\/)|(\\\\)/.test(p)) { | ||||
| 			console.warn("'" + p + "' doesn't seem to be a valid port number, socket path, or pipe"); | ||||
| 		} | ||||
| 
 | ||||
| 		var mw = greenlock.middleware.sanitizeHost(greenlock.middleware(require("redirect-https")())); | ||||
| 		server = require("http").createServer(function(req, res) { | ||||
| 			req.on("error", function(err) { | ||||
| 				console.error("Insecure Request Network Connection Error:"); | ||||
| 				console.error(err); | ||||
| 			}); | ||||
| 			mw(req, res); | ||||
| 		}); | ||||
| 		httpType = "http"; | ||||
| 
 | ||||
| 		return { | ||||
| 			server: server, | ||||
| 			listen: function() { | ||||
| 				return new PromiseA(function(resolve, reject) { | ||||
| 					args[0] = p; | ||||
| 					args.push(function() { | ||||
| 						if (!greenlock.servername) { | ||||
| 							if (Array.isArray(greenlock.approvedDomains) && greenlock.approvedDomains.length) { | ||||
| 								greenlock.servername = greenlock.approvedDomains[0]; | ||||
| 							} | ||||
| 							if (Array.isArray(greenlock.approveDomains) && greenlock.approvedDomains.length) { | ||||
| 								greenlock.servername = greenlock.approvedDomains[0]; | ||||
| 							} | ||||
| 						} | ||||
| 
 | ||||
| 						if (!greenlock.servername) { | ||||
| 							resolve(null); | ||||
| 							return; | ||||
| 						} | ||||
| 
 | ||||
| 						return greenlock | ||||
| 							.check({ domains: [greenlock.servername] }) | ||||
| 							.then(function(certs) { | ||||
| 								if (certs) { | ||||
| 									return { | ||||
| 										key: Buffer.from(certs.privkey, "ascii"), | ||||
| 										cert: Buffer.from(certs.cert + "\r\n" + certs.chain, "ascii") | ||||
| 									}; | ||||
| 								} | ||||
| 								console.info( | ||||
| 									"Fetching certificate for '%s' to use as default for HTTPS server...", | ||||
| 									greenlock.servername | ||||
| 								); | ||||
| 								return new PromiseA(function(resolve, reject) { | ||||
| 									// using SNICallback because all options will be set
 | ||||
| 									greenlock.tlsOptions.SNICallback(greenlock.servername, function(err /*, secureContext*/) { | ||||
| 										if (err) { | ||||
| 											reject(err); | ||||
| 											return; | ||||
| 										} | ||||
| 										return greenlock | ||||
| 											.check({ domains: [greenlock.servername] }) | ||||
| 											.then(function(certs) { | ||||
| 												resolve({ | ||||
| 													key: Buffer.from(certs.privkey, "ascii"), | ||||
| 													cert: Buffer.from(certs.cert + "\r\n" + certs.chain, "ascii") | ||||
| 												}); | ||||
| 											}) | ||||
| 											.catch(reject); | ||||
| 									}); | ||||
| 								}); | ||||
| 							}) | ||||
| 							.then(resolve) | ||||
| 							.catch(reject); | ||||
| 					}); | ||||
| 					server.listen.apply(server, args).on("error", function(e) { | ||||
| 						if (server.listenerCount("error") < 2) { | ||||
| 							console.warn("Did not successfully create http server and bind to port '" + p + "':"); | ||||
| 							explainError(e); | ||||
| 							process.exit(41); | ||||
| 						} | ||||
| 					}); | ||||
| 				}); | ||||
| 			} | ||||
| 		}; | ||||
| 	} | ||||
| 
 | ||||
| 	function _create(port) { | ||||
| 		if (!port) { | ||||
| 			port = 443; | ||||
| 		} | ||||
| 
 | ||||
| 		var parts = String(port).split(":"); | ||||
| 		var p = parts.pop(); | ||||
| 		var addr = parts | ||||
| 			.join(":") | ||||
| 			.replace(/^\[/, "") | ||||
| 			.replace(/\]$/, ""); | ||||
| 		var args = []; | ||||
| 		var httpType; | ||||
| 		var server; | ||||
| 		var validHttpPort = parseInt(p, 10) >= 0; | ||||
| 
 | ||||
| 		if (addr) { | ||||
| 			args[1] = addr; | ||||
| 		} | ||||
| 		if (!validHttpPort && !/(\/)|(\\\\)/.test(p)) { | ||||
| 			console.warn("'" + p + "' doesn't seem to be a valid port number, socket path, or pipe"); | ||||
| 		} | ||||
| 
 | ||||
| 		var https; | ||||
| 		try { | ||||
| 			https = require("spdy"); | ||||
| 			greenlock.tlsOptions.spdy = { protocols: ["h2", "http/1.1"], plain: false }; | ||||
| 			httpType = "http2 (spdy/h2)"; | ||||
| 		} catch (e) { | ||||
| 			https = require("https"); | ||||
| 			httpType = "https"; | ||||
| 		} | ||||
| 		var sniCallback = greenlock.tlsOptions.SNICallback; | ||||
| 		greenlock.tlsOptions.SNICallback = function(domain, cb) { | ||||
| 			sniCallback(domain, function(err, context) { | ||||
| 				cb(err, context); | ||||
| 
 | ||||
| 				if (!context || server._hasDefaultSecureContext) { | ||||
| 					return; | ||||
| 				} | ||||
| 				if (!domain) { | ||||
| 					domain = greenlock.servername; | ||||
| 				} | ||||
| 				if (!domain) { | ||||
| 					return; | ||||
| 				} | ||||
| 
 | ||||
| 				return greenlock | ||||
| 					.check({ domains: [domain] }) | ||||
| 					.then(function(certs) { | ||||
| 						// ignore the case that check doesn't have all the right args here
 | ||||
| 						// to get the same certs that it just got (eventually the right ones will come in)
 | ||||
| 						if (!certs) { | ||||
| 							return; | ||||
| 						} | ||||
| 						if (server.setSecureContext) { | ||||
| 							// only available in node v11.0+
 | ||||
| 							server.setSecureContext({ | ||||
| 								key: Buffer.from(certs.privkey, "ascii"), | ||||
| 								cert: Buffer.from(certs.cert + "\r\n" + certs.chain, "ascii") | ||||
| 							}); | ||||
| 							console.info("Using '%s' as default certificate", domain); | ||||
| 						} else { | ||||
| 							console.info("Setting default certificates dynamically requires node v11.0+. Skipping."); | ||||
| 						} | ||||
| 						server._hasDefaultSecureContext = true; | ||||
| 					}) | ||||
| 					.catch(function(/*e*/) { | ||||
| 						// this may be that the test.example.com was requested, but it's listed
 | ||||
| 						// on the cert for demo.example.com which is in its own directory, not the other
 | ||||
| 						//console.warn("Unusual error: couldn't get newly authorized certificate:");
 | ||||
| 						//console.warn(e.message);
 | ||||
| 					}); | ||||
| 			}); | ||||
| 		}; | ||||
| 		if (greenlock.tlsOptions.cert) { | ||||
| 			server._hasDefaultSecureContext = true; | ||||
| 			if (greenlock.tlsOptions.cert.toString("ascii").split("BEGIN").length < 3) { | ||||
| 				console.warn( | ||||
| 					"Invalid certificate file. 'tlsOptions.cert' should contain cert.pem (certificate file) *and* chain.pem (intermediate certificates) seperated by an extra newline (CRLF)" | ||||
| 				); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		var mw = greenlock.middleware.sanitizeHost(function(req, res) { | ||||
| 			try { | ||||
| 				greenlock.app(req, res); | ||||
| 			} catch (e) { | ||||
| 				console.error("[error] [greenlock.app] Your HTTP handler had an uncaught error:"); | ||||
| 				console.error(e); | ||||
| 				try { | ||||
| 					res.statusCode = 500; | ||||
| 					res.end("Internal Server Error: [Greenlock] HTTP exception logged for user-provided handler."); | ||||
| 				} catch (e) { | ||||
| 					// ignore
 | ||||
| 					// (headers may have already been sent, etc)
 | ||||
| 				} | ||||
| 			} | ||||
| 		}); | ||||
| 		server = https.createServer(greenlock.tlsOptions, function(req, res) { | ||||
| 			/* | ||||
| 			// Don't do this yet
 | ||||
| 			req.on("error", function(err) { | ||||
| 				console.error("HTTPS Request Network Connection Error:"); | ||||
| 				console.error(err); | ||||
| 			}); | ||||
| 			*/ | ||||
| 			mw(req, res); | ||||
| 		}); | ||||
| 		server.type = httpType; | ||||
| 
 | ||||
| 		return { | ||||
| 			server: server, | ||||
| 			listen: function() { | ||||
| 				return new PromiseA(function(resolve) { | ||||
| 					args[0] = p; | ||||
| 					args.push(function() { | ||||
| 						resolve(/*server*/); | ||||
| 					}); | ||||
| 					server.listen.apply(server, args).on("error", function(e) { | ||||
| 						if (server.listenerCount("error") < 2) { | ||||
| 							console.warn("Did not successfully create http server and bind to port '" + p + "':"); | ||||
| 							explainError(e); | ||||
| 							process.exit(41); | ||||
| 						} | ||||
| 					}); | ||||
| 				}); | ||||
| 			} | ||||
| 		}; | ||||
| 	} | ||||
| 
 | ||||
| 	// NOTE: 'greenlock' is just 'opts' renamed
 | ||||
| 	var greenlock = require("greenlock").create(opts); | ||||
| 
 | ||||
| 	if (!opts.app) { | ||||
| 		opts.app = function(req, res) { | ||||
| 			res.end("Hello, World!\nWith Love,\nGreenlock for Express.js"); | ||||
| 		}; | ||||
| 	} | ||||
| 
 | ||||
| 	opts.listen = function(plainPort, port, fnPlain, fn) { | ||||
| 		var server; | ||||
| 		var plainServer; | ||||
| 
 | ||||
| 		// If there is only one handler for the `listening` (i.e. TCP bound) event
 | ||||
| 		// then we want to use it as HTTPS (backwards compat)
 | ||||
| 		if (!fn) { | ||||
| 			fn = fnPlain; | ||||
| 			fnPlain = null; | ||||
| 		} | ||||
| 
 | ||||
| 		var obj1 = _createPlain(plainPort, true); | ||||
| 		var obj2 = _create(port, false); | ||||
| 
 | ||||
| 		plainServer = obj1.server; | ||||
| 		server = obj2.server; | ||||
| 
 | ||||
| 		server.then = obj1.listen().then(function(tlsOptions) { | ||||
| 			if (tlsOptions) { | ||||
| 				if (server.setSecureContext) { | ||||
| 					// only available in node v11.0+
 | ||||
| 					server.setSecureContext(tlsOptions); | ||||
| 					console.info("Using '%s' as default certificate", greenlock.servername); | ||||
| 				} else { | ||||
| 					console.info("Setting default certificates dynamically requires node v11.0+. Skipping."); | ||||
| 				} | ||||
| 				server._hasDefaultSecureContext = true; | ||||
| 			} | ||||
| 			return obj2.listen().then(function() { | ||||
| 				// Report plain http status
 | ||||
| 				if ("function" === typeof fnPlain) { | ||||
| 					fnPlain.apply(plainServer); | ||||
| 				} else if (!fn && !plainServer.listenerCount("listening") && !server.listenerCount("listening")) { | ||||
| 					console.info( | ||||
| 						"[:" + | ||||
| 							(plainServer.address().port || plainServer.address()) + | ||||
| 							"] Handling ACME challenges and redirecting to " + | ||||
| 							server.type | ||||
| 					); | ||||
| 				} | ||||
| 
 | ||||
| 				// Report h2/https status
 | ||||
| 				if ("function" === typeof fn) { | ||||
| 					fn.apply(server); | ||||
| 				} else if (!server.listenerCount("listening")) { | ||||
| 					console.info("[:" + (server.address().port || server.address()) + "] Serving " + server.type); | ||||
| 				} | ||||
| 			}); | ||||
| 		}).then; | ||||
| 
 | ||||
| 		server.unencrypted = plainServer; | ||||
| 		return server; | ||||
| 	}; | ||||
| 	opts.middleware.acme = function(opts) { | ||||
| 		return greenlock.middleware.sanitizeHost(greenlock.middleware(require("redirect-https")(opts))); | ||||
| 	}; | ||||
| 	opts.middleware.secure = function(app) { | ||||
| 		return greenlock.middleware.sanitizeHost(app); | ||||
| 	}; | ||||
| 
 | ||||
| 	return greenlock; | ||||
| }; | ||||
							
								
								
									
										36
									
								
								main.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								main.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,36 @@ | ||||
| "use strict"; | ||||
| 
 | ||||
| // this is the stuff that should run in the main foreground process,
 | ||||
| // whether it's single or master
 | ||||
| 
 | ||||
| var major = process.versions.node.split(".")[0]; | ||||
| var minor = process.versions.node.split(".")[1]; | ||||
| var _hasSetSecureContext = false; | ||||
| var shouldUpgrade = false; | ||||
| 
 | ||||
| // TODO can we trust earlier versions as well?
 | ||||
| if (major >= 12) { | ||||
| 	_hasSetSecureContext = !!require("http2").createSecureServer({}, function() {}).setSecureContext; | ||||
| } else { | ||||
| 	_hasSetSecureContext = !!require("https").createServer({}, function() {}).setSecureContext; | ||||
| } | ||||
| 
 | ||||
| // TODO document in issues
 | ||||
| if (!_hasSetSecureContext) { | ||||
| 	// TODO this isn't necessary if greenlock options are set with options.cert
 | ||||
| 	console.warn("Warning: node " + process.version + " is missing tlsSocket.setSecureContext()."); | ||||
| 	console.warn("         The default certificate may not be set."); | ||||
| 	shouldUpgrade = true; | ||||
| } | ||||
| 
 | ||||
| if (major < 11 || (11 === major && minor < 2)) { | ||||
| 	// https://github.com/nodejs/node/issues/24095
 | ||||
| 	console.warn("Warning: node " + process.version + " is missing tlsSocket.getCertificate()."); | ||||
| 	console.warn("         This is necessary to guard against domain fronting attacks."); | ||||
| 	shouldUpgrade = true; | ||||
| } | ||||
| 
 | ||||
| if (shouldUpgrade) { | ||||
| 	console.warn("Warning: Please upgrade to node v11.2.0 or greater."); | ||||
|   console.warn(); | ||||
| } | ||||
							
								
								
									
										95
									
								
								master.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								master.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,95 @@ | ||||
| "use strict"; | ||||
| 
 | ||||
| require("./main.js"); | ||||
| 
 | ||||
| var Master = module.exports; | ||||
| 
 | ||||
| var cluster = require("cluster"); | ||||
| var os = require("os"); | ||||
| var Greenlock = require("@root/greenlock"); | ||||
| var pkg = require("./package.json"); | ||||
| 
 | ||||
| Master.create = function(opts) { | ||||
| 	var workers = []; | ||||
| 	var resolveCb; | ||||
| 	var readyCb; | ||||
| 	var _kicked = false; | ||||
| 
 | ||||
| 	var packageAgent = pkg.name + "/" + pkg.version; | ||||
| 	if ("string" === typeof opts.packageAgent) { | ||||
| 		opts.packageAgent += " "; | ||||
| 	} else { | ||||
| 		opts.packageAgent = ""; | ||||
| 	} | ||||
| 	opts.packageAgent += packageAgent; | ||||
| 	var greenlock = Greenlock.create(opts); | ||||
| 
 | ||||
| 	var ready = new Promise(function(resolve) { | ||||
| 		resolveCb = resolve; | ||||
| 	}).then(function(fn) { | ||||
| 		readyCb = fn; | ||||
| 	}); | ||||
| 
 | ||||
| 	function kickoff() { | ||||
| 		if (_kicked) { | ||||
| 			return; | ||||
| 		} | ||||
| 		_kicked = true; | ||||
| 
 | ||||
| 		console.log("TODO: start the workers and such..."); | ||||
| 		// handle messages from workers
 | ||||
| 		workers.push(null); | ||||
| 		ready.then(function(fn) { | ||||
| 			// not sure what this API should be yet
 | ||||
| 			fn({ | ||||
| 				//workers: workers.slice(0)
 | ||||
| 			}); | ||||
| 		}); | ||||
| 	} | ||||
| 
 | ||||
| 	var master = { | ||||
| 		worker: function() { | ||||
| 			kickoff(); | ||||
| 			return master; | ||||
| 		}, | ||||
| 		master: function(fn) { | ||||
| 			if (readyCb) { | ||||
| 				throw new Error("can't call master twice"); | ||||
| 			} | ||||
| 			kickoff(); | ||||
| 			resolveCb(fn); | ||||
| 			return master; | ||||
| 		} | ||||
| 	}; | ||||
| }; | ||||
| 
 | ||||
| // opts.approveDomains(options, certs, cb)
 | ||||
| GLE.create = function(opts) { | ||||
| 	GLE._spawnWorkers(opts); | ||||
| 
 | ||||
| 	gl.tlsOptions = {}; | ||||
| 
 | ||||
| 
 | ||||
| 	return master; | ||||
| }; | ||||
| 
 | ||||
| function range(n) { | ||||
| 	return new Array(n).join(",").split(","); | ||||
| } | ||||
| 
 | ||||
| Master._spawnWorkers = function(opts) { | ||||
| 	var numCpus = parseInt(process.env.NUMBER_OF_PROCESSORS, 10) || os.cpus().length; | ||||
| 
 | ||||
| 	var numWorkers = parseInt(opts.numWorkers, 10); | ||||
| 	if (!numWorkers) { | ||||
| 		if (numCpus <= 2) { | ||||
| 			numWorkers = numCpus; | ||||
| 		} else { | ||||
| 			numWorkers = numCpus - 1; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return range(numWorkers).map(function() { | ||||
| 		return cluster.fork(); | ||||
| 	}); | ||||
| }; | ||||
							
								
								
									
										128
									
								
								servers.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								servers.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,128 @@ | ||||
| "use strict"; | ||||
| 
 | ||||
| var Servers = module.exports; | ||||
| 
 | ||||
| var http = require("http"); | ||||
| var HttpMiddleware = require("./http-middleware.js"); | ||||
| var HttpsMiddleware = require("./https-middleware.js"); | ||||
| var sni = require("./sni.js"); | ||||
| 
 | ||||
| Servers.create = function(greenlock, opts) { | ||||
| 	var servers = {}; | ||||
| 	var _httpServer; | ||||
| 	var _httpsServer; | ||||
| 
 | ||||
| 	function startError(e) { | ||||
| 		explainError(e); | ||||
| 		process.exit(1); | ||||
| 	} | ||||
| 
 | ||||
| 	servers.httpServer = function(defaultApp) { | ||||
| 		if (_httpServer) { | ||||
| 			return _httpServer; | ||||
| 		} | ||||
| 
 | ||||
| 		_httpServer = http.createServer(HttpMiddleware.create(opts.greenlock, defaultApp)); | ||||
| 		_httpServer.once("error", startError); | ||||
| 
 | ||||
| 		return _httpServer; | ||||
| 	}; | ||||
| 
 | ||||
| 	servers.httpsServer = function(secureOpts, defaultApp) { | ||||
| 		if (_httpsServer) { | ||||
| 			return _httpsServer; | ||||
| 		} | ||||
| 
 | ||||
| 		if (!secureOpts) { | ||||
| 			secureOpts = {}; | ||||
| 		} | ||||
| 
 | ||||
| 		_httpsServer = createSecureServer( | ||||
| 			wrapDefaultSniCallback(opts, greenlock, secureOpts), | ||||
| 			HttpsMiddleware.create(greenlock, defaultApp) | ||||
| 		); | ||||
| 		_httpsServer.once("error", startError); | ||||
| 
 | ||||
| 		return _httpsServer; | ||||
| 	}; | ||||
| 
 | ||||
| 	servers.serveApp = function(app) { | ||||
| 		return new Promise(function(resolve, reject) { | ||||
| 			if ("function" !== typeof app) { | ||||
| 				reject(new Error("glx.serveApp(app) expects a node/express app in the format `function (req, res) { ... }`")); | ||||
| 				return; | ||||
| 			} | ||||
| 
 | ||||
| 			var plainServer = servers.httpServer(require("redirect-https")()); | ||||
| 			var plainAddr = "0.0.0.0"; | ||||
| 			var plainPort = 80; | ||||
| 			plainServer.listen(plainPort, plainAddr, function() { | ||||
| 				console.info("Listening on", plainAddr + ":" + plainPort, "for ACME challenges, and redirecting to HTTPS"); | ||||
| 
 | ||||
| 				// TODO fetch greenlock.servername
 | ||||
| 				var secureServer = servers.httpsServer(app); | ||||
| 				var secureAddr = "0.0.0.0"; | ||||
| 				var securePort = 443; | ||||
| 				secureServer.listen(securePort, secureAddr, function() { | ||||
| 					console.info("Listening on", secureAddr + ":" + securePort, "for secure traffic"); | ||||
| 
 | ||||
| 					plainServer.removeListener("error", startError); | ||||
| 					secureServer.removeListener("error", startError); | ||||
| 					resolve(); | ||||
| 				}); | ||||
| 			}); | ||||
| 		}); | ||||
| 	}; | ||||
| 	return servers; | ||||
| }; | ||||
| 
 | ||||
| function explainError(e) { | ||||
| 	console.error(); | ||||
| 	console.error("Error: " + e.message); | ||||
| 	if ("EACCES" === e.errno) { | ||||
| 		console.error("You don't have prmission to access '" + e.address + ":" + e.port + "'."); | ||||
| 		console.error('You probably need to use "sudo" or "sudo setcap \'cap_net_bind_service=+ep\' $(which node)"'); | ||||
| 	} else if ("EADDRINUSE" === e.errno) { | ||||
| 		console.error("'" + e.address + ":" + e.port + "' is already being used by some other program."); | ||||
| 		console.error("You probably need to stop that program or restart your computer."); | ||||
| 	} else { | ||||
| 		console.error(e.code + ": '" + e.address + ":" + e.port + "'"); | ||||
| 	} | ||||
| 	console.error(); | ||||
| } | ||||
| 
 | ||||
| function wrapDefaultSniCallback(opts, greenlock, secureOpts) { | ||||
| 	// I'm not sure yet if the original SNICallback
 | ||||
| 	// should be called before or after, so I'm just
 | ||||
| 	// going to delay making that choice until I have the use case
 | ||||
| 	/* | ||||
| 		if (!secureOpts.SNICallback) { | ||||
| 			secureOpts.SNICallback = function(servername, cb) { | ||||
| 				cb(null, null); | ||||
| 			}; | ||||
| 		} | ||||
|   */ | ||||
| 	if (secureOpts.SNICallback) { | ||||
| 		console.warn(); | ||||
| 		console.warn("[warning] Ignoring the given tlsOptions.SNICallback function."); | ||||
| 		console.warn(); | ||||
| 		console.warn("          We're very open to implementing support for this,"); | ||||
| 		console.warn("          we just don't understand the use case yet."); | ||||
| 		console.warn("          Please open an issue to discuss. We'd love to help."); | ||||
| 		console.warn(); | ||||
| 	} | ||||
| 
 | ||||
| 	secureOpts.SNICallback = sni.create(opts, greenlock, secureOpts); | ||||
| 	return secureOpts; | ||||
| } | ||||
| 
 | ||||
| function createSecureServer(secureOpts, fn) { | ||||
| 	var major = process.versions.node.split(".")[0]; | ||||
| 
 | ||||
| 	// TODO can we trust earlier versions as well?
 | ||||
| 	if (major >= 12) { | ||||
| 		return require("http2").createSecureServer(secureOpts, fn); | ||||
| 	} else { | ||||
| 		return require("https").createServer(secureOpts, fn); | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										26
									
								
								single.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								single.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,26 @@ | ||||
| "use strict"; | ||||
| 
 | ||||
| require("./main.js"); | ||||
| 
 | ||||
| var Single = module.exports; | ||||
| var Servers = require("./servers.js"); | ||||
| var Greenlock = require("@root/greenlock"); | ||||
| 
 | ||||
| Single.create = function(opts) { | ||||
| 	var greenlock = Greenlock.create(opts); | ||||
| 	var servers = Servers.create(greenlock, opts); | ||||
| 	//var master = Master.create(opts);
 | ||||
| 
 | ||||
| 	var single = { | ||||
| 		worker: function(fn) { | ||||
| 			fn(servers); | ||||
| 			return single; | ||||
| 		}, | ||||
| 		master: function(/*fn*/) { | ||||
| 			// ignore
 | ||||
| 			//fn(master);
 | ||||
| 			return single; | ||||
| 		} | ||||
| 	}; | ||||
| 	return single; | ||||
| }; | ||||
							
								
								
									
										184
									
								
								sni.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										184
									
								
								sni.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,184 @@ | ||||
| "use strict"; | ||||
| 
 | ||||
| var sni = module.exports; | ||||
| var tls = require("tls"); | ||||
| var servernameRe = /^[a-z0-9\.\-]+$/i; | ||||
| 
 | ||||
| // a nice, round, irrational number - about every 6¼ hours
 | ||||
| var refreshOffset = Math.round(Math.PI * 2 * (60 * 60 * 1000)); | ||||
| // and another, about 15 minutes
 | ||||
| var refreshStagger = Math.round(Math.PI * 5 * (60 * 1000)); | ||||
| // and another, about 30 seconds
 | ||||
| var smallStagger = Math.round(Math.PI * (30 * 1000)); | ||||
| 
 | ||||
| //secureOpts.SNICallback = sni.create(opts, greenlock, secureOpts);
 | ||||
| sni.create = function(opts, greenlock, secureOpts) { | ||||
| 	var _cache = {}; | ||||
| 	var defaultServername = opts.servername || greenlock.servername; | ||||
| 
 | ||||
| 	if (secureOpts.cert) { | ||||
| 		// Note: it's fine if greenlock.servername is undefined,
 | ||||
| 		// but if the caller wants this to auto-renew, they should define it
 | ||||
| 		_cache[defaultServername] = { | ||||
| 			refreshAt: 0, | ||||
| 			secureContext: tls.createSecureContext(secureOpts) | ||||
| 		}; | ||||
| 	} | ||||
| 
 | ||||
| 	return getSecureContext; | ||||
| 
 | ||||
| 	function notify(ev, args) { | ||||
| 		try { | ||||
| 			// TODO _notify() or notify()?
 | ||||
| 			(opts.notify || greenlock.notify || greenlock._notify)(ev, args); | ||||
| 		} catch (e) { | ||||
| 			console.error(e); | ||||
| 			console.error(ev, args); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	function getSecureContext(servername, cb) { | ||||
| 		if ("string" !== typeof servername) { | ||||
| 			// this will never happen... right? but stranger things have...
 | ||||
| 			console.error("[sanity fail] non-string servername:", servername); | ||||
| 			cb(new Error("invalid servername"), null); | ||||
| 			return; | ||||
| 		} | ||||
| 
 | ||||
| 		var secureContext = getCachedContext(servername); | ||||
| 		if (secureContext) { | ||||
| 			cb(null, secureContext); | ||||
| 			return; | ||||
| 		} | ||||
| 
 | ||||
| 		getFreshContext(servername) | ||||
| 			.then(function(secureContext) { | ||||
| 				if (secureContext) { | ||||
| 					cb(null, secureContext); | ||||
| 					return; | ||||
| 				} | ||||
| 				// Note: this does not replace tlsSocket.setSecureContext()
 | ||||
| 				// as it only works when SNI has been sent
 | ||||
| 				cb(null, getDefaultContext()); | ||||
| 			}) | ||||
| 			.catch(function(err) { | ||||
| 				if (!err.context) { | ||||
| 					err.context = "sni_callback"; | ||||
| 				} | ||||
| 				notify("error", err); | ||||
| 				cb(err); | ||||
| 			}); | ||||
| 	} | ||||
| 
 | ||||
| 	function getCachedMeta(servername) { | ||||
| 		var meta = _cache[servername]; | ||||
| 		if (!meta) { | ||||
| 			if (!_cache[wildname(servername)]) { | ||||
| 				return null; | ||||
| 			} | ||||
| 		} | ||||
| 		return meta; | ||||
| 	} | ||||
| 
 | ||||
| 	function getCachedContext(servername) { | ||||
| 		var meta = getCachedMeta(servername); | ||||
| 		if (!meta) { | ||||
| 			return null; | ||||
| 		} | ||||
| 
 | ||||
| 		if (!meta.refreshAt || Date.now() >= meta.refreshAt) { | ||||
| 			getFreshContext(servername).catch(function(e) { | ||||
| 				if (!e.context) { | ||||
| 					e.context = "sni_background_refresh"; | ||||
| 				} | ||||
| 				notify("error", e); | ||||
| 			}); | ||||
| 		} | ||||
| 
 | ||||
| 		return meta.secureContext; | ||||
| 	} | ||||
| 
 | ||||
| 	function getFreshContext(servername) { | ||||
| 		var meta = getCachedMeta(servername); | ||||
| 		if (!meta && !validServername(servername)) { | ||||
| 			return Promise.resolve(null); | ||||
| 		} | ||||
| 
 | ||||
| 		if (meta) { | ||||
| 			// prevent stampedes
 | ||||
| 			meta.refreshAt = Date.now() + randomRefreshOffset(); | ||||
| 		} | ||||
| 
 | ||||
| 		// TODO greenlock.get({ servername: servername })
 | ||||
| 		// TODO don't get unknown certs at all, rely on auto-updates from greenlock
 | ||||
| 		// Note: greenlock.renew() will return an existing fresh cert or issue a new one
 | ||||
| 		return greenlock.renew({ servername: servername }).then(function(matches) { | ||||
| 			var meta = getCachedMeta(servername); | ||||
| 			if (!meta) { | ||||
| 				meta = _cache[servername] = { secureContext: {} }; | ||||
| 			} | ||||
| 			// prevent from being punked by bot trolls
 | ||||
| 			meta.refreshAt = Date.now() + smallStagger; | ||||
| 
 | ||||
| 			// nothing to do
 | ||||
| 			if (!matches.length) { | ||||
| 				return null; | ||||
| 			} | ||||
| 
 | ||||
| 			// we only care about the first one
 | ||||
| 			var pems = matches[0].pems; | ||||
| 			var site = matches[0].site; | ||||
| 			var match = matches[0]; | ||||
| 			if (!pems || !pems.cert) { | ||||
| 				// nothing to do
 | ||||
| 				// (and the error should have been reported already)
 | ||||
| 				return null; | ||||
| 			} | ||||
| 
 | ||||
| 			meta = { | ||||
| 				refreshAt: Date.now() + randomRefreshOffset(), | ||||
| 				secureContext: tls.createSecureContext({ | ||||
| 					// TODO support passphrase-protected privkeys
 | ||||
| 					key: pems.privkey, | ||||
| 					cert: pems.cert + "\n" + pems.chain + "\n" | ||||
| 				}) | ||||
| 			}; | ||||
| 
 | ||||
| 			// copy this same object into every place
 | ||||
| 			[match.altnames || site.altnames || [match.subject || site.subject]].forEach(function(altname) { | ||||
| 				_cache[altname] = meta; | ||||
| 			}); | ||||
| 		}); | ||||
| 	} | ||||
| 
 | ||||
| 	function getDefaultContext() { | ||||
| 		return getCachedContext(defaultServername); | ||||
| 	} | ||||
| }; | ||||
| 
 | ||||
| // whenever we need to know when to refresh next
 | ||||
| function randomRefreshOffset() { | ||||
| 	var stagger = Math.round(refreshStagger / 2) - Math.round(Math.random() * refreshStagger); | ||||
| 	return refreshOffset + stagger; | ||||
| } | ||||
| 
 | ||||
| function validServername(servername) { | ||||
| 	// format and (lightly) sanitize sni so that users can be naive
 | ||||
| 	// and not have to worry about SQL injection or fs discovery
 | ||||
| 
 | ||||
| 	servername = (servername || "").toLowerCase(); | ||||
| 	// hostname labels allow a-z, 0-9, -, and are separated by dots
 | ||||
| 	// _ is sometimes allowed, but not as a "hostname", and not by Let's Encrypt ACME
 | ||||
| 	// REGEX // https://www.codeproject.com/Questions/1063023/alphanumeric-validation-javascript-without-regex
 | ||||
| 	return servernameRe.test(servername) && -1 === servername.indexOf(".."); | ||||
| } | ||||
| 
 | ||||
| function wildname(servername) { | ||||
| 	return ( | ||||
| 		"*." + | ||||
| 		servername | ||||
| 			.split(".") | ||||
| 			.slice(1) | ||||
| 			.join(".") | ||||
| 	); | ||||
| } | ||||
							
								
								
									
										74
									
								
								worker.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								worker.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,74 @@ | ||||
| "use strict"; | ||||
| 
 | ||||
| var Worker = module.exports; | ||||
| 
 | ||||
| Worker.create = function(opts) { | ||||
| 	var greenlock = { | ||||
| 		// rename presentChallenge?
 | ||||
| 		getAcmeHttp01ChallengeResponse: presentChallenge, | ||||
| 		notify: notifyMaster, | ||||
| 		get: greenlockRenew | ||||
| 	}; | ||||
| 
 | ||||
| 	var worker = { | ||||
| 		worker: function(fn) { | ||||
| 			var servers = require("./servers.js").create(greenlock, opts); | ||||
| 			fn(servers); | ||||
| 			return worker; | ||||
| 		}, | ||||
| 		master: function() { | ||||
| 			// ignore
 | ||||
| 			return worker; | ||||
| 		} | ||||
| 	}; | ||||
| 	return worker; | ||||
| }; | ||||
| 
 | ||||
| function greenlockRenew(args) { | ||||
| 	return request("renew", { | ||||
| 		servername: args.servername | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| function presentChallenge(args) { | ||||
| 	return request("challenge-response", { | ||||
| 		servername: args.servername, | ||||
| 		token: args.token | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| function request(typename, msg) { | ||||
| 	return new Promise(function(resolve, reject) { | ||||
| 		var rnd = Math.random() | ||||
| 			.slice(2) | ||||
| 			.toString(16); | ||||
| 		var id = "greenlock:" + rnd; | ||||
| 		var timeout; | ||||
| 
 | ||||
| 		function getResponse(msg) { | ||||
| 			if (msg.id !== id) { | ||||
| 				return; | ||||
| 			} | ||||
| 			clearTimeout(timeout); | ||||
| 			resolve(msg); | ||||
| 		} | ||||
| 
 | ||||
| 		process.on("message", getResponse); | ||||
| 		msg.id = msg; | ||||
| 		msg.type = typename; | ||||
| 		process.send(msg); | ||||
| 
 | ||||
| 		timeout = setTimeout(function() { | ||||
| 			process.removeListener("message", getResponse); | ||||
| 			reject(new Error("process message timeout")); | ||||
| 		}, 30 * 1000); | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| function notifyMaster(ev, args) { | ||||
| 	process.on("message", { | ||||
| 		type: "notification", | ||||
| 		event: ev, | ||||
| 		parameters: args | ||||
| 	}); | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user