use std::io::Read; use std::net::IpAddr; use std::str::FromStr; use actix_web::{AsyncResponder, FutureResponse, HttpMessage, HttpRequest, HttpResponse, Scope}; use actix_web::http::Method; use actix_web::http::StatusCode; use bytes::{Buf, Bytes, IntoBuf}; use cloudflare::{Cloudflare, zones}; use futures::future::Future; use crate::config::model::Config; use crate::server::error::{APIError, Error}; use crate::server::error::Result; use crate::server::middleware::api_auth::APIAuthRootAndZone; use crate::server::router::AppState; use crate::server::util; trait RecordType { fn get_record_type(&self) -> zones::dns::RecordType; } impl RecordType for IpAddr { fn get_record_type(&self) -> zones::dns::RecordType { match &self { IpAddr::V4(_) => zones::dns::RecordType::A, IpAddr::V6(_) => zones::dns::RecordType::AAAA } } } pub fn route(scope: Scope) -> Scope { scope .resource("address", |r| r.method(Method::GET).f(get_request_address)) .nested("{root}/{zone}", |zone_scope| { zone_scope .middleware(APIAuthRootAndZone) .resource("", |r| r.method(Method::GET).f(get_address)) .resource("update", |r| { r.method(Method::GET).f(update_address_automatically); r.method(Method::POST).f(update_address_manually) }) }) } fn update_address(address: String) -> String { info!("Updating Address {}", address); address } fn parse_remote_info(remote_info: &str) -> Result { let mut remote_address = String::from(remote_info); if remote_address.contains(':') { let last_colon_index = remote_address.rfind(':').unwrap(); let _port = remote_address.split_off(last_colon_index); if remote_address.starts_with('[') && remote_address.ends_with(']') { remote_address = String::from(remote_address.trim_matches(|c| c == '[' || c == ']')) } match IpAddr::from_str(&remote_address) { Ok(v) => Ok(v), Err(e) => Err(APIError::new(&format!("Address Parse Error \"{}\"", remote_address), Some(Box::from(e)))) } } else { match IpAddr::from_str(&remote_address) { Ok(v) => Ok(v), Err(e) => Err(APIError::new(&format!("Address Parse Error \"{}\"", remote_address), Some(Box::from(e)))) } } } fn determine_request_address(req: &HttpRequest) -> Result> { match req.connection_info().remote() { Some(remote_info) => match parse_remote_info(remote_info) { Ok(addr) => Ok(Some(addr)), Err(e) => { error!("{:?}", e); Err(Error::from(e)) } } None => Ok(None), } } fn get_request_address(req: &HttpRequest) -> HttpResponse { match determine_request_address(&req) { Ok(potential_addr) => match potential_addr { Some(addr) => HttpResponse::build(StatusCode::OK) .content_type("text/plain") .body(format!("{}", addr)), None => HttpResponse::build(StatusCode::BAD_REQUEST).finish() }, Err(e) => { error!("{:?}", e); HttpResponse::build(StatusCode::BAD_REQUEST).finish() } } } fn find_dns_record( cloudflare: &Cloudflare, zone_id: &str, record_type: zones::dns::RecordType, record_name: &str, ) -> Result> { match zones::dns::list_dns_of_type(cloudflare, &zone_id, record_type) { Ok(dns_entries) => { debug!("Looking for dns record: {}", record_name); for dns_entry in dns_entries { debug!("record {:?}", dns_entry); if dns_entry.name == record_name { return Ok(Some(dns_entry)); } } Ok(None) } Err(e) => { error!("{:?}", e); Err(Error::from(e)) } } } fn get_address(req: &HttpRequest) -> HttpResponse { let root_match = util::get_match_value(req, "root"); let zone_match = util::get_match_value(req, "zone"); let cloudflare: &Cloudflare = &req.state().cloudflare; if root_match.is_none() || zone_match.is_none() { HttpResponse::BadRequest().into() } else { let root = root_match.unwrap(); let zone = zone_match.unwrap(); match util::get_domain_from_root(&root) { Some(domain) => { let zone_id = match zones::get_zoneid(cloudflare, &domain) { Ok(zone_id) => zone_id, Err(e) => { error!("{:?}", e); return HttpResponse::BadRequest().into(); } }; match find_dns_record(cloudflare, &zone_id, zones::dns::RecordType::A, &format!("{}.{}", &zone, &root)) { Ok(record) => match record { Some(dns_entry) => HttpResponse::build(StatusCode::OK) .content_type("text/plain") .body(format!("{}", dns_entry.content)), None => HttpResponse::NotFound().into() }, Err(e) => { error!("{:?}", e); HttpResponse::InternalServerError().into() } } }, None => HttpResponse::BadRequest().into() } } } fn update_address_automatically(req: &HttpRequest) -> HttpResponse { let root_match = util::get_match_value(req, "root"); let zone_match = util::get_match_value(req, "zone"); let cloudflare: &Cloudflare = &req.state().cloudflare; if root_match.is_none() || zone_match.is_none() { HttpResponse::BadRequest().into() } else { let root = root_match.unwrap(); let zone = zone_match.unwrap(); let request_address = match determine_request_address(&req) { Ok(potential_addr) => match potential_addr { Some(addr) => addr, None => return HttpResponse::build(StatusCode::BAD_REQUEST).finish() }, Err(e) => { error!("{:?}", e); return HttpResponse::build(StatusCode::BAD_REQUEST).finish(); } }; let request_address_content = format!("{}", request_address); let record_name = format!("{}.{}", &zone, &root); match util::get_domain_from_root(&root) { Some(domain) => { let zone_id = match zones::get_zoneid(cloudflare, &domain) { Ok(zone_id) => zone_id, Err(e) => { error!("{:?}", e); return HttpResponse::BadRequest().into(); } }; match find_dns_record(cloudflare, &zone_id, request_address.get_record_type(), &record_name) { Ok(record) => match record { Some(dns_entry) => { match zones::dns::update_dns_entry( cloudflare, &dns_entry.zone_id, &dns_entry.id, request_address.get_record_type(), &dns_entry.name, &request_address_content) { Ok(result_record) => HttpResponse::build(StatusCode::OK) .content_type("text/plain") .body(format!("{}", result_record.content)), Err(e) => { error!("{:?}", e); HttpResponse::InternalServerError().into() } } } None => match zones::dns::create_dns_entry( cloudflare, &zone_id, request_address.get_record_type(), &record_name, &request_address_content) { Ok(result_record) => HttpResponse::build(StatusCode::OK) .content_type("text/plain") .body(format!("{}", result_record.content)), Err(e) => { error!("{:?}", e); HttpResponse::InternalServerError().into() } } }, Err(e) => { error!("{:?}", e); HttpResponse::InternalServerError().into() } } }, None => HttpResponse::BadRequest().into() } } } fn update_address_manually(req: &HttpRequest) -> FutureResponse { req.body() .limit(48) .from_err() .and_then(|bytes: Bytes| { let mut buffer = String::new(); match bytes.into_buf().reader().read_to_string(&mut buffer) { Ok(_) => Ok(HttpResponse::Ok() .content_type("text/plain") .body(update_address(buffer))), Err(_) => Ok(HttpResponse::build(StatusCode::BAD_REQUEST).finish()), } }) .responder() }