A cloudflare backed DDNS service written in Rust
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

248 lines
9.5 KiB

  1. use std::io::Read;
  2. use std::net::IpAddr;
  3. use std::str::FromStr;
  4. use actix_web::{AsyncResponder, FutureResponse, HttpMessage, HttpRequest, HttpResponse, Scope};
  5. use actix_web::http::Method;
  6. use actix_web::http::StatusCode;
  7. use bytes::{Buf, Bytes, IntoBuf};
  8. use cloudflare::{Cloudflare, zones};
  9. use futures::future::Future;
  10. use crate::config::model::Config;
  11. use crate::server::error::{APIError, Error};
  12. use crate::server::error::Result;
  13. use crate::server::middleware::api_auth::APIAuthRootAndZone;
  14. use crate::server::router::AppState;
  15. use crate::server::util;
  16. trait RecordType {
  17. fn get_record_type(&self) -> zones::dns::RecordType;
  18. }
  19. impl RecordType for IpAddr {
  20. fn get_record_type(&self) -> zones::dns::RecordType {
  21. match &self {
  22. IpAddr::V4(_) => zones::dns::RecordType::A,
  23. IpAddr::V6(_) => zones::dns::RecordType::AAAA
  24. }
  25. }
  26. }
  27. pub fn route(scope: Scope<AppState>) -> Scope<AppState> {
  28. scope
  29. .resource("address", |r| r.method(Method::GET).f(get_request_address))
  30. .nested("{root}/{zone}", |zone_scope| {
  31. zone_scope
  32. .middleware(APIAuthRootAndZone)
  33. .resource("", |r| r.method(Method::GET).f(get_address))
  34. .resource("update", |r| {
  35. r.method(Method::GET).f(update_address_automatically);
  36. r.method(Method::POST).f(update_address_manually)
  37. })
  38. })
  39. }
  40. fn update_address(address: String) -> String {
  41. info!("Updating Address {}", address);
  42. address
  43. }
  44. fn parse_remote_info(remote_info: &str) -> Result<IpAddr> {
  45. let mut remote_address = String::from(remote_info);
  46. if remote_address.contains(':') {
  47. let last_colon_index = remote_address.rfind(':').unwrap();
  48. let _port = remote_address.split_off(last_colon_index);
  49. if remote_address.starts_with('[') && remote_address.ends_with(']') {
  50. remote_address = String::from(remote_address.trim_matches(|c| c == '[' || c == ']'))
  51. }
  52. match IpAddr::from_str(&remote_address) {
  53. Ok(v) => Ok(v),
  54. Err(e) => Err(APIError::new(&format!("Address Parse Error \"{}\"", remote_address), Some(Box::from(e))))
  55. }
  56. } else {
  57. match IpAddr::from_str(&remote_address) {
  58. Ok(v) => Ok(v),
  59. Err(e) => Err(APIError::new(&format!("Address Parse Error \"{}\"", remote_address), Some(Box::from(e))))
  60. }
  61. }
  62. }
  63. fn determine_request_address(req: &HttpRequest<AppState>) -> Result<Option<IpAddr>> {
  64. match req.connection_info().remote() {
  65. Some(remote_info) => match parse_remote_info(remote_info) {
  66. Ok(addr) => Ok(Some(addr)),
  67. Err(e) => {
  68. error!("{:?}", e);
  69. Err(Error::from(e))
  70. }
  71. }
  72. None => Ok(None),
  73. }
  74. }
  75. fn get_request_address(req: &HttpRequest<AppState>) -> HttpResponse {
  76. match determine_request_address(&req) {
  77. Ok(potential_addr) => match potential_addr {
  78. Some(addr) => HttpResponse::build(StatusCode::OK)
  79. .content_type("text/plain")
  80. .body(format!("{}", addr)),
  81. None => HttpResponse::build(StatusCode::BAD_REQUEST).finish()
  82. },
  83. Err(e) => {
  84. error!("{:?}", e);
  85. HttpResponse::build(StatusCode::BAD_REQUEST).finish()
  86. }
  87. }
  88. }
  89. fn find_dns_record(
  90. cloudflare: &Cloudflare,
  91. zone_id: &str,
  92. record_type: zones::dns::RecordType,
  93. record_name: &str,
  94. ) -> Result<Option<zones::dns::DnsRecord>> {
  95. match zones::dns::list_dns_of_type(cloudflare, &zone_id, record_type) {
  96. Ok(dns_entries) => {
  97. debug!("Looking for dns record: {}", record_name);
  98. for dns_entry in dns_entries {
  99. debug!("record {:?}", dns_entry);
  100. if dns_entry.name == record_name {
  101. return Ok(Some(dns_entry));
  102. }
  103. }
  104. Ok(None)
  105. }
  106. Err(e) => {
  107. error!("{:?}", e);
  108. Err(Error::from(e))
  109. }
  110. }
  111. }
  112. fn get_address(req: &HttpRequest<AppState>) -> HttpResponse {
  113. let root_match = util::get_match_value(req, "root");
  114. let zone_match = util::get_match_value(req, "zone");
  115. let cloudflare: &Cloudflare = &req.state().cloudflare;
  116. if root_match.is_none() || zone_match.is_none() {
  117. HttpResponse::BadRequest().into()
  118. } else {
  119. let root = root_match.unwrap();
  120. let zone = zone_match.unwrap();
  121. match util::get_domain_from_root(&root) {
  122. Some(domain) => {
  123. let zone_id = match zones::get_zoneid(cloudflare, &domain) {
  124. Ok(zone_id) => zone_id,
  125. Err(e) => {
  126. error!("{:?}", e);
  127. return HttpResponse::BadRequest().into();
  128. }
  129. };
  130. match find_dns_record(cloudflare, &zone_id, zones::dns::RecordType::A, &format!("{}.{}", &zone, &root)) {
  131. Ok(record) => match record {
  132. Some(dns_entry) => HttpResponse::build(StatusCode::OK)
  133. .content_type("text/plain")
  134. .body(format!("{}", dns_entry.content)),
  135. None => HttpResponse::NotFound().into()
  136. },
  137. Err(e) => {
  138. error!("{:?}", e);
  139. HttpResponse::InternalServerError().into()
  140. }
  141. }
  142. },
  143. None => HttpResponse::BadRequest().into()
  144. }
  145. }
  146. }
  147. fn update_address_automatically(req: &HttpRequest<AppState>) -> HttpResponse {
  148. let root_match = util::get_match_value(req, "root");
  149. let zone_match = util::get_match_value(req, "zone");
  150. let cloudflare: &Cloudflare = &req.state().cloudflare;
  151. if root_match.is_none() || zone_match.is_none() {
  152. HttpResponse::BadRequest().into()
  153. } else {
  154. let root = root_match.unwrap();
  155. let zone = zone_match.unwrap();
  156. let request_address = match determine_request_address(&req) {
  157. Ok(potential_addr) => match potential_addr {
  158. Some(addr) => addr,
  159. None => return HttpResponse::build(StatusCode::BAD_REQUEST).finish()
  160. },
  161. Err(e) => {
  162. error!("{:?}", e);
  163. return HttpResponse::build(StatusCode::BAD_REQUEST).finish();
  164. }
  165. };
  166. let request_address_content = format!("{}", request_address);
  167. let record_name = format!("{}.{}", &zone, &root);
  168. match util::get_domain_from_root(&root) {
  169. Some(domain) => {
  170. let zone_id = match zones::get_zoneid(cloudflare, &domain) {
  171. Ok(zone_id) => zone_id,
  172. Err(e) => {
  173. error!("{:?}", e);
  174. return HttpResponse::BadRequest().into();
  175. }
  176. };
  177. match find_dns_record(cloudflare, &zone_id, request_address.get_record_type(), &record_name) {
  178. Ok(record) => match record {
  179. Some(dns_entry) => {
  180. match zones::dns::update_dns_entry(
  181. cloudflare,
  182. &dns_entry.zone_id,
  183. &dns_entry.id,
  184. request_address.get_record_type(),
  185. &dns_entry.name,
  186. &request_address_content) {
  187. Ok(result_record) => HttpResponse::build(StatusCode::OK)
  188. .content_type("text/plain")
  189. .body(format!("{}", result_record.content)),
  190. Err(e) => {
  191. error!("{:?}", e);
  192. HttpResponse::InternalServerError().into()
  193. }
  194. }
  195. }
  196. None => match zones::dns::create_dns_entry(
  197. cloudflare,
  198. &zone_id,
  199. request_address.get_record_type(),
  200. &record_name,
  201. &request_address_content) {
  202. Ok(result_record) => HttpResponse::build(StatusCode::OK)
  203. .content_type("text/plain")
  204. .body(format!("{}", result_record.content)),
  205. Err(e) => {
  206. error!("{:?}", e);
  207. HttpResponse::InternalServerError().into()
  208. }
  209. }
  210. },
  211. Err(e) => {
  212. error!("{:?}", e);
  213. HttpResponse::InternalServerError().into()
  214. }
  215. }
  216. },
  217. None => HttpResponse::BadRequest().into()
  218. }
  219. }
  220. }
  221. fn update_address_manually(req: &HttpRequest<AppState>) -> FutureResponse<HttpResponse> {
  222. req.body()
  223. .limit(48)
  224. .from_err()
  225. .and_then(|bytes: Bytes| {
  226. let mut buffer = String::new();
  227. match bytes.into_buf().reader().read_to_string(&mut buffer) {
  228. Ok(_) => Ok(HttpResponse::Ok()
  229. .content_type("text/plain")
  230. .body(update_address(buffer))),
  231. Err(_) => Ok(HttpResponse::build(StatusCode::BAD_REQUEST).finish()),
  232. }
  233. })
  234. .responder()
  235. }