1 #![allow(
2 clippy::inconsistent_digit_grouping,
3 clippy::uninlined_format_args,
4 clippy::unusual_byte_groupings
5 )]
6
7 extern crate autocfg;
8 #[cfg(feature = "bindgen")]
9 extern crate bindgen;
10 extern crate cc;
11 #[cfg(feature = "vendored")]
12 extern crate openssl_src;
13 extern crate pkg_config;
14 #[cfg(target_env = "msvc")]
15 extern crate vcpkg;
16
17 use std::collections::HashSet;
18 use std::env;
19 use std::ffi::OsString;
20 use std::path::{Path, PathBuf};
21 mod cfgs;
22
23 mod find_normal;
24 #[cfg(feature = "vendored")]
25 mod find_vendored;
26 mod run_bindgen;
27
28 #[derive(PartialEq)]
29 enum Version {
30 Openssl3xx,
31 Openssl11x,
32 Openssl10x,
33 Libressl,
34 Boringssl,
35 }
36
env_inner(name: &str) -> Option<OsString>37 fn env_inner(name: &str) -> Option<OsString> {
38 let var = env::var_os(name);
39 println!("cargo:rerun-if-env-changed={}", name);
40
41 match var {
42 Some(ref v) => println!("{} = {}", name, v.to_string_lossy()),
43 None => println!("{} unset", name),
44 }
45
46 var
47 }
48
env(name: &str) -> Option<OsString>49 fn env(name: &str) -> Option<OsString> {
50 let prefix = env::var("TARGET").unwrap().to_uppercase().replace('-', "_");
51 let prefixed = format!("{}_{}", prefix, name);
52 env_inner(&prefixed).or_else(|| env_inner(name))
53 }
54
find_openssl(target: &str) -> (Vec<PathBuf>, PathBuf)55 fn find_openssl(target: &str) -> (Vec<PathBuf>, PathBuf) {
56 #[cfg(feature = "vendored")]
57 {
58 // vendor if the feature is present, unless
59 // OPENSSL_NO_VENDOR exists and isn't `0`
60 if env("OPENSSL_NO_VENDOR").map_or(true, |s| s == "0") {
61 return find_vendored::get_openssl(target);
62 }
63 }
64 find_normal::get_openssl(target)
65 }
66
check_ssl_kind()67 fn check_ssl_kind() {
68 if cfg!(feature = "unstable_boringssl") {
69 println!("cargo:rustc-cfg=boringssl");
70 println!("cargo:boringssl=true");
71 // BoringSSL does not have any build logic, exit early
72 std::process::exit(0);
73 }
74 }
75
main()76 fn main() {
77 check_rustc_versions();
78
79 check_ssl_kind();
80
81 let target = env::var("TARGET").unwrap();
82
83 let (lib_dirs, include_dir) = find_openssl(&target);
84
85 if !lib_dirs.iter().all(|p| Path::new(p).exists()) {
86 panic!("OpenSSL library directory does not exist: {:?}", lib_dirs);
87 }
88 if !Path::new(&include_dir).exists() {
89 panic!(
90 "OpenSSL include directory does not exist: {}",
91 include_dir.to_string_lossy()
92 );
93 }
94
95 for lib_dir in lib_dirs.iter() {
96 println!(
97 "cargo:rustc-link-search=native={}",
98 lib_dir.to_string_lossy()
99 );
100 }
101 println!("cargo:include={}", include_dir.to_string_lossy());
102
103 let version = postprocess(&[include_dir]);
104
105 let libs_env = env("OPENSSL_LIBS");
106 let libs = match libs_env.as_ref().and_then(|s| s.to_str()) {
107 Some(v) => {
108 if v.is_empty() {
109 vec![]
110 } else {
111 v.split(':').collect()
112 }
113 }
114 None => match version {
115 Version::Openssl10x if target.contains("windows") => vec!["ssleay32", "libeay32"],
116 Version::Openssl3xx | Version::Openssl11x if target.contains("windows-msvc") => {
117 vec!["libssl", "libcrypto"]
118 }
119 _ => vec!["ssl", "crypto"],
120 },
121 };
122
123 let kind = determine_mode(&lib_dirs, &libs);
124 for lib in libs.into_iter() {
125 println!("cargo:rustc-link-lib={}={}", kind, lib);
126 }
127
128 if kind == "static" && target.contains("windows") {
129 println!("cargo:rustc-link-lib=dylib=gdi32");
130 println!("cargo:rustc-link-lib=dylib=user32");
131 println!("cargo:rustc-link-lib=dylib=crypt32");
132 println!("cargo:rustc-link-lib=dylib=ws2_32");
133 println!("cargo:rustc-link-lib=dylib=advapi32");
134 }
135 }
136
check_rustc_versions()137 fn check_rustc_versions() {
138 let cfg = autocfg::new();
139
140 if cfg.probe_rustc_version(1, 31) {
141 println!("cargo:rustc-cfg=const_fn");
142 }
143 }
144
145 #[allow(clippy::let_and_return)]
postprocess(include_dirs: &[PathBuf]) -> Version146 fn postprocess(include_dirs: &[PathBuf]) -> Version {
147 let version = validate_headers(include_dirs);
148
149 // Never run bindgen for BoringSSL, if it was needed we already ran it.
150 if version != Version::Boringssl {
151 #[cfg(feature = "bindgen")]
152 run_bindgen::run(&include_dirs);
153 }
154
155 version
156 }
157
158 /// Validates the header files found in `include_dir` and then returns the
159 /// version string of OpenSSL.
160 #[allow(clippy::manual_strip)] // we need to support pre-1.45.0
validate_headers(include_dirs: &[PathBuf]) -> Version161 fn validate_headers(include_dirs: &[PathBuf]) -> Version {
162 // This `*-sys` crate only works with OpenSSL 1.0.1, 1.0.2, 1.1.0, 1.1.1 and 3.0.0.
163 // To correctly expose the right API from this crate, take a look at
164 // `opensslv.h` to see what version OpenSSL claims to be.
165 //
166 // OpenSSL has a number of build-time configuration options which affect
167 // various structs and such. Since OpenSSL 1.1.0 this isn't really a problem
168 // as the library is much more FFI-friendly, but 1.0.{1,2} suffer this problem.
169 //
170 // To handle all this conditional compilation we slurp up the configuration
171 // file of OpenSSL, `opensslconf.h`, and then dump out everything it defines
172 // as our own #[cfg] directives. That way the `ossl10x.rs` bindings can
173 // account for compile differences and such.
174 println!("cargo:rerun-if-changed=build/expando.c");
175 let mut gcc = cc::Build::new();
176 for include_dir in include_dirs {
177 gcc.include(include_dir);
178 }
179 let expanded = match gcc.file("build/expando.c").try_expand() {
180 Ok(expanded) => expanded,
181 Err(e) => {
182 panic!(
183 "
184 Header expansion error:
185 {:?}
186
187 Failed to find OpenSSL development headers.
188
189 You can try fixing this setting the `OPENSSL_DIR` environment variable
190 pointing to your OpenSSL installation or installing OpenSSL headers package
191 specific to your distribution:
192
193 # On Ubuntu
194 sudo apt-get install libssl-dev
195 # On Arch Linux
196 sudo pacman -S openssl
197 # On Fedora
198 sudo dnf install openssl-devel
199 # On Alpine Linux
200 apk add openssl-dev
201
202 See rust-openssl documentation for more information:
203
204 https://docs.rs/openssl
205 ",
206 e
207 );
208 }
209 };
210 let expanded = String::from_utf8(expanded).unwrap();
211
212 let mut enabled = vec![];
213 let mut openssl_version = None;
214 let mut libressl_version = None;
215 let mut is_boringssl = false;
216 for line in expanded.lines() {
217 let line = line.trim();
218
219 let openssl_prefix = "RUST_VERSION_OPENSSL_";
220 let new_openssl_prefix = "RUST_VERSION_NEW_OPENSSL_";
221 let libressl_prefix = "RUST_VERSION_LIBRESSL_";
222 let boringsl_prefix = "RUST_OPENSSL_IS_BORINGSSL";
223 let conf_prefix = "RUST_CONF_";
224 if line.starts_with(openssl_prefix) {
225 let version = &line[openssl_prefix.len()..];
226 openssl_version = Some(parse_version(version));
227 } else if line.starts_with(new_openssl_prefix) {
228 let version = &line[new_openssl_prefix.len()..];
229 openssl_version = Some(parse_new_version(version));
230 } else if line.starts_with(libressl_prefix) {
231 let version = &line[libressl_prefix.len()..];
232 libressl_version = Some(parse_version(version));
233 } else if line.starts_with(conf_prefix) {
234 enabled.push(&line[conf_prefix.len()..]);
235 } else if line.starts_with(boringsl_prefix) {
236 is_boringssl = true;
237 }
238 }
239
240 if is_boringssl {
241 println!("cargo:rustc-cfg=boringssl");
242 println!("cargo:boringssl=true");
243 run_bindgen::run_boringssl(include_dirs);
244 return Version::Boringssl;
245 }
246
247 // We set this for any non-BoringSSL lib.
248 println!("cargo:rustc-cfg=openssl");
249
250 for enabled in &enabled {
251 println!("cargo:rustc-cfg=osslconf=\"{}\"", enabled);
252 }
253 println!("cargo:conf={}", enabled.join(","));
254
255 for cfg in cfgs::get(openssl_version, libressl_version) {
256 println!("cargo:rustc-cfg={}", cfg);
257 }
258
259 if let Some(libressl_version) = libressl_version {
260 println!("cargo:libressl_version_number={:x}", libressl_version);
261
262 let major = (libressl_version >> 28) as u8;
263 let minor = (libressl_version >> 20) as u8;
264 let fix = (libressl_version >> 12) as u8;
265 let (major, minor, fix) = match (major, minor, fix) {
266 (2, 5, 0) => ('2', '5', '0'),
267 (2, 5, 1) => ('2', '5', '1'),
268 (2, 5, 2) => ('2', '5', '2'),
269 (2, 5, _) => ('2', '5', 'x'),
270 (2, 6, 0) => ('2', '6', '0'),
271 (2, 6, 1) => ('2', '6', '1'),
272 (2, 6, 2) => ('2', '6', '2'),
273 (2, 6, _) => ('2', '6', 'x'),
274 (2, 7, _) => ('2', '7', 'x'),
275 (2, 8, 0) => ('2', '8', '0'),
276 (2, 8, 1) => ('2', '8', '1'),
277 (2, 8, _) => ('2', '8', 'x'),
278 (2, 9, 0) => ('2', '9', '0'),
279 (2, 9, _) => ('2', '9', 'x'),
280 (3, 0, 0) => ('3', '0', '0'),
281 (3, 0, 1) => ('3', '0', '1'),
282 (3, 0, _) => ('3', '0', 'x'),
283 (3, 1, 0) => ('3', '1', '0'),
284 (3, 1, _) => ('3', '1', 'x'),
285 (3, 2, 0) => ('3', '2', '0'),
286 (3, 2, 1) => ('3', '2', '1'),
287 (3, 2, _) => ('3', '2', 'x'),
288 (3, 3, 0) => ('3', '3', '0'),
289 (3, 3, 1) => ('3', '3', '1'),
290 (3, 3, _) => ('3', '3', 'x'),
291 (3, 4, 0) => ('3', '4', '0'),
292 (3, 4, _) => ('3', '4', 'x'),
293 (3, 5, _) => ('3', '5', 'x'),
294 (3, 6, 0) => ('3', '6', '0'),
295 (3, 6, _) => ('3', '6', 'x'),
296 (3, 7, 0) => ('3', '7', '0'),
297 (3, 7, 1) => ('3', '7', '1'),
298 _ => version_error(),
299 };
300
301 println!("cargo:libressl=true");
302 println!("cargo:libressl_version={}{}{}", major, minor, fix);
303 println!("cargo:version=101");
304 Version::Libressl
305 } else {
306 let openssl_version = openssl_version.unwrap();
307 println!("cargo:version_number={:x}", openssl_version);
308
309 if openssl_version >= 0x4_00_00_00_0 {
310 version_error()
311 } else if openssl_version >= 0x3_00_00_00_0 {
312 Version::Openssl3xx
313 } else if openssl_version >= 0x1_01_01_00_0 {
314 println!("cargo:version=111");
315 Version::Openssl11x
316 } else if openssl_version >= 0x1_01_00_06_0 {
317 println!("cargo:version=110");
318 println!("cargo:patch=f");
319 Version::Openssl11x
320 } else if openssl_version >= 0x1_01_00_00_0 {
321 println!("cargo:version=110");
322 Version::Openssl11x
323 } else if openssl_version >= 0x1_00_02_00_0 {
324 println!("cargo:version=102");
325 Version::Openssl10x
326 } else if openssl_version >= 0x1_00_01_00_0 {
327 println!("cargo:version=101");
328 Version::Openssl10x
329 } else {
330 version_error()
331 }
332 }
333 }
334
version_error() -> !335 fn version_error() -> ! {
336 panic!(
337 "
338
339 This crate is only compatible with OpenSSL (version 1.0.1 through 1.1.1, or 3.0.0), or LibreSSL 2.5
340 through 3.7.1, but a different version of OpenSSL was found. The build is now aborting
341 due to this version mismatch.
342
343 "
344 );
345 }
346
347 // parses a string that looks like "0x100020cfL"
348 #[allow(deprecated)] // trim_right_matches is now trim_end_matches
349 #[allow(clippy::match_like_matches_macro)] // matches macro requires rust 1.42.0
parse_version(version: &str) -> u64350 fn parse_version(version: &str) -> u64 {
351 // cut off the 0x prefix
352 assert!(version.starts_with("0x"));
353 let version = &version[2..];
354
355 // and the type specifier suffix
356 let version = version.trim_right_matches(|c: char| match c {
357 '0'..='9' | 'a'..='f' | 'A'..='F' => false,
358 _ => true,
359 });
360
361 u64::from_str_radix(version, 16).unwrap()
362 }
363
364 // parses a string that looks like 3_0_0
parse_new_version(version: &str) -> u64365 fn parse_new_version(version: &str) -> u64 {
366 println!("version: {}", version);
367 let mut it = version.split('_');
368 let major = it.next().unwrap().parse::<u64>().unwrap();
369 let minor = it.next().unwrap().parse::<u64>().unwrap();
370 let patch = it.next().unwrap().parse::<u64>().unwrap();
371
372 (major << 28) | (minor << 20) | (patch << 4)
373 }
374
375 /// Given a libdir for OpenSSL (where artifacts are located) as well as the name
376 /// of the libraries we're linking to, figure out whether we should link them
377 /// statically or dynamically.
determine_mode(libdirs: &[PathBuf], libs: &[&str]) -> &'static str378 fn determine_mode(libdirs: &[PathBuf], libs: &[&str]) -> &'static str {
379 // First see if a mode was explicitly requested
380 let kind = env("OPENSSL_STATIC");
381 match kind.as_ref().and_then(|s| s.to_str()) {
382 Some("0") => return "dylib",
383 Some(_) => return "static",
384 None => {}
385 }
386
387 // Next, see what files we actually have to link against, and see what our
388 // possibilities even are.
389 let mut files = HashSet::new();
390 for dir in libdirs {
391 for path in dir
392 .read_dir()
393 .unwrap()
394 .map(|e| e.unwrap())
395 .map(|e| e.file_name())
396 .filter_map(|e| e.into_string().ok())
397 {
398 files.insert(path);
399 }
400 }
401 let can_static = libs
402 .iter()
403 .all(|l| files.contains(&format!("lib{}.a", l)) || files.contains(&format!("{}.lib", l)));
404 let can_dylib = libs.iter().all(|l| {
405 files.contains(&format!("lib{}.so", l))
406 || files.contains(&format!("{}.dll", l))
407 || files.contains(&format!("lib{}.dylib", l))
408 });
409 match (can_static, can_dylib) {
410 (true, false) => return "static",
411 (false, true) => return "dylib",
412 (false, false) => {
413 panic!(
414 "OpenSSL libdir at `{:?}` does not contain the required files \
415 to either statically or dynamically link OpenSSL",
416 libdirs
417 );
418 }
419 (true, true) => {}
420 }
421
422 // Ok, we've got not explicit preference and can *either* link statically or
423 // link dynamically. In the interest of "security upgrades" and/or "best
424 // practices with security libs", let's link dynamically.
425 "dylib"
426 }
427