1use std::path::Path;
7use std::{env, fs};
8
9use config::builder::DefaultState;
10use config::{Config, ConfigBuilder, ConfigError, File};
11use derive_more::Display;
12
13use serde::{Deserialize, Serialize};
14use url::Url;
15
16#[derive(Debug, Clone, Deserialize, Eq, PartialEq)]
17pub struct Server {
18 pub port: u32,
19 pub domain: String,
20 pub cookie_secret: String,
21 pub ip: String,
22 pub url_prefix: Option<String>,
24 pub proxy_has_tls: bool,
25}
26
27#[derive(Debug, Clone, Deserialize, Eq, PartialEq)]
28pub struct Captcha {
29 pub salt: String,
30 pub gc: u64,
31 pub runners: Option<usize>,
32 pub queue_length: usize,
33 pub enable_stats: bool,
34 pub default_difficulty_strategy: DefaultDifficultyStrategy,
35}
36
37#[derive(Debug, Clone, Deserialize, Eq, PartialEq)]
38pub struct DefaultDifficultyStrategy {
39 pub avg_traffic_difficulty: u32,
40 pub avg_traffic_time: Option<u32>,
41 pub peak_sustainable_traffic_difficulty: u32,
42 pub peak_sustainable_traffic_time: Option<u32>,
43 pub broke_my_site_traffic_time: Option<u32>,
44 pub broke_my_site_traffic_difficulty: u32,
45 pub duration: u32,
46}
47
48#[derive(Debug, Clone, Deserialize, Eq, PartialEq)]
49pub struct Smtp {
50 pub from: String,
51 pub reply: String,
52 pub url: String,
53 pub username: String,
54 pub password: String,
55 pub port: u16,
56}
57
58impl Server {
59 pub fn get_ip(&self) -> String {
60 format!("{}:{}", self.ip, self.port)
61 }
62}
63
64#[derive(Deserialize, Serialize, Display, Eq, PartialEq, Clone, Debug)]
65#[serde(rename_all = "lowercase")]
66pub enum DBType {
67 #[display(fmt = "postgres")]
68 Postgres,
69 #[display(fmt = "maria")]
70 Maria,
71}
72
73impl DBType {
74 fn from_url(url: &Url) -> Result<Self, ConfigError> {
75 match url.scheme() {
76 "mysql" => Ok(Self::Maria),
77 "postgres" => Ok(Self::Postgres),
78 _ => Err(ConfigError::Message("Unknown database type".into())),
79 }
80 }
81}
82
83#[derive(Debug, Clone, Deserialize, Eq, PartialEq)]
84pub struct Database {
85 pub url: String,
86 pub pool: u32,
87 pub database_type: DBType,
88}
89
90#[derive(Debug, Clone, Deserialize, Eq, PartialEq)]
91pub struct Redis {
92 pub url: String,
93 pub pool: u32,
94}
95
96#[derive(Debug, Clone, Deserialize, Eq, PartialEq)]
97pub struct Survey {
98 pub nodes: Vec<url::Url>,
99 pub rate_limit: u64,
100 pub instance_root_url: Url,
101}
102
103#[derive(Debug, Clone, Deserialize, Eq, PartialEq)]
104pub struct Settings {
105 pub debug: bool,
106 pub commercial: bool,
107 pub source_code: String,
108 pub allow_registration: bool,
109 pub allow_demo: bool,
110 pub database: Database,
111 pub survey: Option<Survey>,
112 pub redis: Option<Redis>,
113 pub server: Server,
114 pub captcha: Captcha,
115 pub smtp: Option<Smtp>,
116}
117
118const ENV_VAR_CONFIG: [(&str, &str); 32] = [
119 ("debug", "MCAPTCHA_debug"),
121 ("commercial", "MCAPTCHA_commercial"),
122 ("source_code", "MCAPTCHA_source_code"),
123 ("allow_registration", "MCAPTCHA_allow_registration"),
124 ("allow_demo", "MCAPTCHA_allow_demo"),
125
126 ("database.url", "DATABASE_URL"),
128 ("database.pool", "MCAPTCHA_database_POOL"),
129
130 ("redis.url", "MCAPTCHA_redis_URL"),
132 ("redis.pool", "MCAPTCHA_redis_POOL"),
133
134 ("server.port", "PORT"),
136 ("server.domain", "MCAPTCHA_server_DOMAIN"),
137 ("server.cookie_secret", "MCAPTCHA__server_COOKIE_SECRET"),
138 ("server.ip", "MCAPTCHA__server_IP"),
139 ("server.proxy_has_tls", "MCAPTCHA__server_PROXY_HAS_TLS"),
140
141
142 ("captcha.salt", "MCAPTCHA_captcha_SALT"),
144 ("captcha.gc", "MCAPTCHA_captcha_GC"),
145 ("captcha.runners", "MCAPTCHA_captcha_RUNNERS"),
146 ("captcha.queue_length", "MCAPTCHA_captcha_QUEUE_LENGTH"),
147 ("captcha.enable_stats", "MCAPTCHA_captcha_ENABLE_STATS"),
148 ("captcha.default_difficulty_strategy.avg_traffic_difficulty", "MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_avg_traffic_difficulty"),
149 ("captcha.default_difficulty_strategy.broke_my_site_traffic_difficulty", "MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_broke_my_site_traffic_difficulty"),
150 ("captcha.default_difficulty_strategy.peak_sustainable_traffic_difficulty",
151 "MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_peak_sustainable_traffic_difficulty"),
152 ( "captcha.default_difficulty_strategy.duration",
153 "MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_duration"
154 ),
155 ("captcha.default_difficulty_strategy.avg_traffic_time", "MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_avg_traffic_time"),
156 ("captcha.default_difficulty_strategy.peak_sustainable_traffic_time", "MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_peak_sustainable_traffic_time"),
157 ("captcha.default_difficulty_strategy.broke_my_site_traffic_time", "MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_broke_my_site_traffic_time"),
158
159
160 ("smtp.from", "MCAPTCHA_smtp_FROM"),
162 ("smtp.reply", "MCAPTCHA_smtp_REPLY"),
163 ("smtp.url", "MCAPTCHA_smtp_URL"),
164 ("smtp.username", "MCAPTCHA_smtp_USERNAME"),
165 ("smtp.password", "MCAPTCHA_smtp_PASSWORD"),
166 ("smtp.port", "MCAPTCHA_smtp_PORT"),
167
168
169
170];
171
172const DEPRECATED_ENV_VARS: [(&str, &str); 23] = [
173 ("debug", "MCAPTCHA_DEBUG"),
174 ("commercial", "MCAPTCHA_COMMERCIAL"),
175 ("source_code", "MCAPTCHA_SOURCE_CODE"),
176 ("allow_registration", "MCAPTCHA_ALLOW_REGISTRATION"),
177 ("allow_demo", "MCAPTCHA_ALLOW_DEMO"),
178 ("redis.pool", "MCAPTCHA_REDIS_POOL"),
179 ("redis.url", "MCAPTCHA_REDIS_URL"),
180 ("server.port", "MCAPTCHA_SERVER_PORT"),
181 ("server.ip", "MCAPTCHA_SERVER_IP"),
182 ("server.domain", "MCAPTCHA_SERVER_DOMAIN"),
183 ("server.cookie_secret", "MCAPTCHA_SERVER_COOKIE_SECRET"),
184 ("server.proxy_has_tls", "MCAPTCHA_SERVER_PROXY_HAS_TLS"),
185 ("captcha.salt", "MCAPTCHA_CAPTCHA_SALT"),
186 ("captcha.gc", "MCAPTCHA_CAPTCHA_GC"),
187 (
188 "captcha.default_difficulty_strategy.avg_traffic_difficulty",
189 "MCAPTCHA_CAPTCHA_AVG_TRAFFIC_DIFFICULTY",
190 ),
191 (
192 "captcha.default_difficulty_strategy.peak_sustainable_traffic_difficulty",
193 "MCAPTCHA_CAPTCHA_PEAK_TRAFFIC_DIFFICULTY",
194 ),
195 (
196 "captcha.default_difficulty_strategy.broke_my_site_traffic_difficulty",
197 "MCAPTCHA_CAPTCHA_BROKE_MY_SITE_TRAFFIC",
198 ),
199 ("smtp.from", "MCAPTCHA_SMTP_FROM"),
200 ("smtp.reply", "MCAPTCHA_SMTP_REPLY_TO"),
201 ("smtp.url", "MCAPTCHA_SMTP_URL"),
202 ("smtp.username", "MCAPTCHA_SMTP_USERNAME"),
203 ("smtp.password", "MCAPTCHA_SMTP_PASSWORD"),
204 ("smtp.port", "MCAPTCHA_SMTP_PORT"),
205];
206
207impl Settings {
208 pub fn new() -> Result<Self, ConfigError> {
209 let mut s = Config::builder();
210
211 const CURRENT_DIR: &str = "./config/default.toml";
212 const ETC: &str = "/etc/mcaptcha/config.toml";
213
214 s = s
215 .set_default("capatcha.enable_stats", true.to_string())
216 .expect("unable to set capatcha.enable_stats default config");
217
218 s = s
224 .set_default("database.database_type", DBType::Postgres.to_string())
225 .expect("unable to set database.database_type default config");
226
227 if let Ok(path) = env::var("MCAPTCHA_CONFIG") {
228 let absolute_path = Path::new(&path).canonicalize().unwrap();
229 log::info!(
230 "Loading config file from {}",
231 absolute_path.to_str().unwrap()
232 );
233 s = s.add_source(File::with_name(absolute_path.to_str().unwrap()));
234 } else if Path::new(CURRENT_DIR).exists() {
235 let absolute_path = fs::canonicalize(CURRENT_DIR).unwrap();
236 log::info!(
237 "Loading config file from {}",
238 absolute_path.to_str().unwrap()
239 );
240 s = s.add_source(File::with_name(absolute_path.to_str().unwrap()));
242 } else if Path::new(ETC).exists() {
243 log::info!("{}", format!("Loading config file from {}", ETC));
244 s = s.add_source(File::with_name(ETC));
245 } else {
246 log::warn!("Configuration file not found");
247 }
248
249 s = Self::env_override(s);
250
251 let mut settings = s.build()?.try_deserialize::<Settings>()?;
252 settings.check_url();
253
254 settings.set_database_type();
255
256 Ok(settings)
257 }
258 fn check_easy_captcha_config(&self) {
259 let s = &self.captcha.default_difficulty_strategy;
260 if s.avg_traffic_time.is_some() {
261 if s.broke_my_site_traffic_time.is_none()
262 || s.peak_sustainable_traffic_time.is_none()
263 {
264 panic!("if captcha.default_difficulty_strategy.avg_traffic_time is set, then captcha.default_difficulty_strategy.broke_my_site_traffic_time and captcha.default_difficulty_strategy.peak_sustainable_traffic_time must also be set");
265 }
266 }
267 if s.peak_sustainable_traffic_time.is_some() {
268 if s.avg_traffic_time.is_none() || s.peak_sustainable_traffic_time.is_none()
269 {
270 panic!("if captcha.default_difficulty_strategy.peak_sustainable_traffic_time is set, then captcha.default_difficulty_strategy.broke_my_site_traffic_time and captcha.default_difficulty_strategy.avg_traffic_time must also be set");
271 }
272 }
273 if s.broke_my_site_traffic_time.is_some() {
274 if s.avg_traffic_time.is_none() || s.peak_sustainable_traffic_time.is_none()
275 {
276 panic!("if captcha.default_difficulty_strategy.broke_my_site_traffic_time is set, then captcha.default_difficulty_strategy.peak_sustainable_traffic_time and captcha.default_difficulty_strategy.avg_traffic_time must also be set");
277 }
278 }
279 }
280
281 fn env_override(mut s: ConfigBuilder<DefaultState>) -> ConfigBuilder<DefaultState> {
282 for (parameter, env_var_name) in DEPRECATED_ENV_VARS.iter() {
283 if let Ok(val) = env::var(env_var_name) {
284 log::warn!(
285 "Found {env_var_name}. {env_var_name} will be deprecated soon. Please see https://github.com/mCaptcha/mCaptcha/blob/master/docs/CONFIGURATION.md for latest environment variable names"
286 );
287 s = s.set_override(parameter, val).unwrap();
288 }
289 }
290
291 for (parameter, env_var_name) in ENV_VAR_CONFIG.iter() {
292 if let Ok(val) = env::var(env_var_name) {
293 log::debug!(
294 "Overriding [{parameter}] with environment variable {env_var_name}"
295 );
296 s = s.set_override(parameter, val).unwrap();
297 }
298 }
299
300 s
301 }
302
303 fn set_database_type(&mut self) {
304 let url = Url::parse(&self.database.url)
305 .expect("couldn't parse Database URL and detect database type");
306 self.database.database_type = DBType::from_url(&url).unwrap();
307 }
308
309 fn check_url(&self) {
310 Url::parse(&self.source_code)
311 .expect("Please enter a URL for source_code in settings");
312 }
313}
314
315#[cfg(test)]
316mod tests {
317
318 use super::*;
319
320 #[test]
321 fn deprecated_env_override_works() {
322 use crate::tests::get_settings;
323 let init_settings = get_settings();
324 let mut new_settings;
326
327 macro_rules! helper {
328
329
330 ($env:expr, $val:expr, $val_typed:expr, $($param:ident).+) => {
331 println!("Setting env var {} to {} for test", $env, $val);
332 env::set_var($env, $val);
333 new_settings = get_settings();
334 assert_eq!(new_settings.$($param).+, $val_typed);
335 assert_ne!(new_settings.$($param).+, init_settings.$($param).+);
336 env::remove_var($env);
337 };
338
339
340 ($env:expr, $val:expr, $($param:ident).+) => {
341 helper!($env, $val.to_string(), $val, $($param).+);
342 };
343 }
344
345 helper!("MCAPTCHA_DEBUG", !init_settings.debug, debug);
347 helper!("MCAPTCHA_COMMERCIAL", !init_settings.commercial, commercial);
348 helper!(
349 "MCAPTCHA_ALLOW_REGISTRATION",
350 !init_settings.allow_registration,
351 allow_registration
352 );
353 helper!("MCAPTCHA_ALLOW_DEMO", !init_settings.allow_demo, allow_demo);
354
355 let env = "MCAPTCHA_REDIS_URL";
359 let val = "redis://redis.example.org";
360 println!("Setting env var {} to {} for test", env, val);
361 env::set_var(env, val);
362 new_settings = get_settings();
363 assert_eq!(new_settings.redis.as_ref().unwrap().url, val);
364 assert_ne!(
365 new_settings.redis.as_ref().unwrap().url,
366 init_settings.redis.as_ref().unwrap().url
367 );
368 env::remove_var(env);
369
370 let env = "MCAPTCHA_REDIS_POOL";
372 let val = 999;
373 println!("Setting env var {} to {} for test", env, val);
374 env::set_var(env, val.to_string());
375 new_settings = get_settings();
376 assert_eq!(new_settings.redis.as_ref().unwrap().pool, val);
377 assert_ne!(
378 new_settings.redis.as_ref().unwrap().pool,
379 init_settings.redis.as_ref().unwrap().pool
380 );
381 env::remove_var(env);
382
383 helper!("PORT", 0, server.port);
384 helper!("MCAPTCHA_SERVER_DOMAIN", "example.org", server.domain);
385 helper!(
386 "MCAPTCHA_SERVER_COOKIE_SECRET",
387 "dafasdfsdf",
388 server.cookie_secret
389 );
390 helper!("MCAPTCHA_SERVER_IP", "9.9.9.9", server.ip);
391 helper!("MCAPTCHA_SERVER_PROXY_HAS_TLS", true, server.proxy_has_tls);
392
393 helper!("MCAPTCHA_CAPTCHA_SALT", "foobarasdfasdf", captcha.salt);
396 helper!("MCAPTCHA_CAPTCHA_GC", 500, captcha.gc);
397 helper!(
398 "MCAPTCHA_captcha_RUNNERS",
399 "500",
400 Some(500),
401 captcha.runners
402 );
403
404 helper!(
405 "MCAPTCHA_CAPTCHA_AVG_TRAFFIC_DIFFICULTY",
406 999,
407 captcha.default_difficulty_strategy.avg_traffic_difficulty
408 );
409 helper!(
410 "MCAPTCHA_CAPTCHA_PEAK_TRAFFIC_DIFFICULTY",
411 999,
412 captcha
413 .default_difficulty_strategy
414 .peak_sustainable_traffic_difficulty
415 );
416 helper!(
417 "MCAPTCHA_CAPTCHA_BROKE_MY_SITE_TRAFFIC",
418 999,
419 captcha
420 .default_difficulty_strategy
421 .broke_my_site_traffic_difficulty
422 );
423
424 let vals = [
427 "MCAPTCHA_SMTP_FROM",
428 "MCAPTCHA_SMTP_REPLY_TO",
429 "MCAPTCHA_SMTP_URL",
430 "MCAPTCHA_SMTP_USERNAME",
431 "MCAPTCHA_SMTP_PASSWORD",
432 "MCAPTCHA_SMTP_PORT",
433 ];
434 for env in vals.iter() {
435 println!("Setting env var {} to {} for test", env, env);
436 env::set_var(env, env);
437 }
438
439 let port = 9999;
440 env::set_var("MCAPTCHA_SMTP_PORT", port.to_string());
441
442 new_settings = get_settings();
443 let smtp_new = new_settings.smtp.as_ref().unwrap();
444 let smtp_old = init_settings.smtp.as_ref().unwrap();
445 assert_eq!(smtp_new.from, "MCAPTCHA_SMTP_FROM");
446 assert_eq!(smtp_new.reply, "MCAPTCHA_SMTP_REPLY_TO");
447 assert_eq!(smtp_new.username, "MCAPTCHA_SMTP_USERNAME");
448 assert_eq!(smtp_new.password, "MCAPTCHA_SMTP_PASSWORD");
449 assert_eq!(smtp_new.port, port);
450 assert_ne!(smtp_new, smtp_old);
451
452 for env in vals.iter() {
453 env::remove_var(env);
454 }
455 }
456
457 #[test]
458 fn env_override_works() {
459 use crate::tests::get_settings;
460 let init_settings = get_settings();
461 let mut new_settings;
463
464 macro_rules! helper {
465
466
467 ($env:expr, $val:expr, $val_typed:expr, $($param:ident).+) => {
468 println!("Setting env var {} to {} for test", $env, $val);
469 env::set_var($env, $val);
470 new_settings = get_settings();
471 assert_eq!(new_settings.$($param).+, $val_typed);
472 assert_ne!(new_settings.$($param).+, init_settings.$($param).+);
473 env::remove_var($env);
474 };
475
476
477 ($env:expr, $val:expr, $($param:ident).+) => {
478 helper!($env, $val.to_string(), $val, $($param).+);
479 };
480 }
481
482 helper!("MCAPTCHA_debug", false, debug);
484 helper!("MCAPTCHA_commercial", true, commercial);
485 helper!("MCAPTCHA_allow_registration", false, allow_registration);
486 helper!("MCAPTCHA_allow_demo", false, allow_demo);
487
488 helper!(
491 "DATABASE_URL",
492 "postgres://postgres:password@localhost:5432/postgres",
493 database.url
494 );
495 assert_eq!(new_settings.database.database_type, DBType::Postgres);
496 helper!(
497 "DATABASE_URL",
498 "mysql://maria:password@localhost/maria",
499 database.url
500 );
501 assert_eq!(new_settings.database.database_type, DBType::Maria);
502 helper!("MCAPTCHA_database_POOL", 1000, database.pool);
503
504 let env = "MCAPTCHA_redis_URL";
508 let val = "redis://redis.example.org";
509 println!("Setting env var {} to {} for test", env, val);
510 env::set_var(env, val);
511 new_settings = get_settings();
512 assert_eq!(new_settings.redis.as_ref().unwrap().url, val);
513 assert_ne!(
514 new_settings.redis.as_ref().unwrap().url,
515 init_settings.redis.as_ref().unwrap().url
516 );
517 env::remove_var(env);
518
519 let env = "MCAPTCHA_redis_POOL";
521 let val = 999;
522 println!("Setting env var {} to {} for test", env, val);
523 env::set_var(env, val.to_string());
524 new_settings = get_settings();
525 assert_eq!(new_settings.redis.as_ref().unwrap().pool, val);
526 assert_ne!(
527 new_settings.redis.as_ref().unwrap().pool,
528 init_settings.redis.as_ref().unwrap().pool
529 );
530 env::remove_var(env);
531
532 helper!("PORT", 0, server.port);
533 helper!("MCAPTCHA_server_DOMAIN", "example.org", server.domain);
534 helper!(
535 "MCAPTCHA__server_COOKIE_SECRET",
536 "dafasdfsdf",
537 server.cookie_secret
538 );
539 helper!("MCAPTCHA__server_IP", "9.9.9.9", server.ip);
540 helper!("MCAPTCHA__server_PROXY_HAS_TLS", true, server.proxy_has_tls);
541
542 helper!("MCAPTCHA_captcha_SALT", "foobarasdfasdf", captcha.salt);
545 helper!("MCAPTCHA_captcha_GC", 500, captcha.gc);
546 helper!(
547 "MCAPTCHA_captcha_RUNNERS",
548 "500",
549 Some(500),
550 captcha.runners
551 );
552
553 helper!("MCAPTCHA_captcha_QUEUE_LENGTH", 500, captcha.queue_length);
554 helper!("MCAPTCHA_captcha_ENABLE_STATS", false, captcha.enable_stats);
555 helper!(
556 "MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_avg_traffic_difficulty",
557 999,
558 captcha.default_difficulty_strategy.avg_traffic_difficulty
559 );
560 helper!("MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_peak_sustainable_traffic_difficulty", 999 , captcha.default_difficulty_strategy.peak_sustainable_traffic_difficulty);
561 helper!("MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_broke_my_site_traffic_difficulty", 999 , captcha.default_difficulty_strategy.broke_my_site_traffic_difficulty);
562 helper!(
563 "MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_duration",
564 999,
565 captcha.default_difficulty_strategy.duration
566 );
567 helper!(
568 "MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_avg_traffic_time",
569 "10",
570 Some(10),
571 captcha.default_difficulty_strategy.avg_traffic_time
572 );
573
574 helper!(
575 "MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_peak_sustainable_traffic_time",
576 "20",
577 Some(20),
578 captcha
579 .default_difficulty_strategy
580 .peak_sustainable_traffic_time
581 );
582
583 helper!(
584 "MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_broke_my_site_traffic_time",
585 "30",
586 Some(30),
587 captcha
588 .default_difficulty_strategy
589 .broke_my_site_traffic_time
590 );
591
592 let vals = [
595 "MCAPTCHA_smtp_FROM",
596 "MCAPTCHA_smtp_REPLY",
597 "MCAPTCHA_smtp_URL",
598 "MCAPTCHA_smtp_USERNAME",
599 "MCAPTCHA_smtp_PASSWORD",
600 "MCAPTCHA_smtp_PORT",
601 ];
602 for env in vals.iter() {
603 println!("Setting env var {} to {} for test", env, env);
604 env::set_var(env, env);
605 }
606
607 let port = 9999;
608 env::set_var("MCAPTCHA_smtp_PORT", port.to_string());
609
610 new_settings = get_settings();
611 let smtp_new = new_settings.smtp.as_ref().unwrap();
612 let smtp_old = init_settings.smtp.as_ref().unwrap();
613 assert_eq!(smtp_new.from, "MCAPTCHA_smtp_FROM");
614 assert_eq!(smtp_new.reply, "MCAPTCHA_smtp_REPLY");
615 assert_eq!(smtp_new.username, "MCAPTCHA_smtp_USERNAME");
616 assert_eq!(smtp_new.password, "MCAPTCHA_smtp_PASSWORD");
617 assert_eq!(smtp_new.port, port);
618 assert_ne!(smtp_new, smtp_old);
619
620 for env in vals.iter() {
621 env::remove_var(env);
622 }
623 }
624
625 }