using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.IO; using System.Net; using System.Threading; using System.Net.Http; using System.Net.Http.Headers; using System.Data; using System.Web.Helpers; using System.Web.Razor.Text; using System.ComponentModel; namespace Cloudfare_DNS_Updater { public class Program { static bool silent = false; public static readonly string[] IPHelperURLS = new string[] { "http://ifconfig.me/ip", "http://ifconfig.co/ip", "http://ident.me/" }; public enum StatusCode { Normal, IpUpdated, IpGetFailiure, FileWriteFailiure, GetRecordFailiure, SetRecordFailiure } static Task[] currentTasks; static void Main(string[] args) { StatusCode status = StatusCode.Normal; if (args.Contains("\\s")) { silent = true; } // Load the data to be used object[] loadState = LoadData(Environment.CurrentDirectory + "\\Credentials.dat"); status = (StatusCode)loadState[0]; if(status == StatusCode.Normal) { List authorities = (List)loadState[1]; foreach (Authority authority in authorities) { status = Task.Run(() => GetRequest(authority)).Result; Console.WriteLine(status.ToString()); } } if(currentTasks != null && currentTasks.Length > 0) { Task.WaitAll(currentTasks); } Environment.Exit((int)status); } static async Task GetRequest(Authority authority) { StatusCode status = StatusCode.Normal; string currentIp = ""; string localIp = ""; string type = ""; int ttl = 0; bool proxiedstate = false; using (HttpClient ipGrabber = new HttpClient()) { bool validIp = false; int domainInc = 0; while (!validIp && domainInc < IPHelperURLS.Length) { HttpResponseMessage httpResponse = await ipGrabber.GetAsync(IPHelperURLS[domainInc]); if (httpResponse.IsSuccessStatusCode) { string provided_ip = await httpResponse.Content.ReadAsStringAsync(); provided_ip = provided_ip.Trim(); IPAddress address = null; if (IPAddress.TryParse(provided_ip, out address)) { localIp = provided_ip; validIp = true; Console.WriteLine("Our IP: " + provided_ip); } } domainInc++; } } if (localIp != "") { HttpClient httpClient = new HttpClient(); httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", " Bearer " + authority.KeyTag.EvaluateAll()); httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/json"); HttpCompletionOption httpCompletionOption = HttpCompletionOption.ResponseContentRead; // Get the current settings HttpResponseMessage response = await httpClient.GetAsync(Authority.cloudfare_api_url + "zones/" + authority.ZoneTag.EvaluateAll() + "/dns_records/" + authority.DnsTag.EvaluateAll(), httpCompletionOption, CancellationToken.None); if (response.IsSuccessStatusCode) { using (HttpContent content = response.Content) { Console.WriteLine("Retrieving IP data from the server"); string s = await content.ReadAsStringAsync(); dynamic recieved_content = Json.Decode(s); currentIp = recieved_content.result.content; type = recieved_content.result.type; ttl = recieved_content.result.ttl; proxiedstate = recieved_content.result.proxied; } // Now check if the IP matches... if (currentIp != localIp) { Console.WriteLine("IP {0} doesn't match our current IP {1}. Updating...", currentIp, localIp); Dictionary UpdateContent = new Dictionary(); UpdateContent.Add("type", type); UpdateContent.Add("name", authority.DomainTag.EvaluateAll()); UpdateContent.Add("content", localIp); // IP Field. Update this as necessary UpdateContent.Add("ttl", ttl); UpdateContent.Add("proxied", proxiedstate); string updateContent = Json.Encode(UpdateContent); byte[] putBytes = Encoding.UTF8.GetBytes(updateContent); HttpContent putContent = new ByteArrayContent(putBytes); HttpResponseMessage putResponse = await httpClient.PutAsync(Authority.cloudfare_api_url + "zones/" + authority.ZoneTag.EvaluateAll() + "/dns_records/" + authority.DnsTag.EvaluateAll(), putContent); if (!putResponse.IsSuccessStatusCode) { status = StatusCode.SetRecordFailiure; } else { status = StatusCode.IpUpdated; } Console.WriteLine("Got {0} updating IP to {1}", putResponse.StatusCode, localIp); } else { Console.WriteLine("DNS IP {0} matches our IP", currentIp); } } else { status = StatusCode.GetRecordFailiure; } } else { status = StatusCode.IpGetFailiure; } return status; } public static object[] LoadData(string filePath) { StatusCode status = StatusCode.Normal; // Decode or encode try { Console.WriteLine("Reading data..."); List authorities = new List(5); using (FileStream fileStream = File.OpenRead(filePath)) { const string EndTag = "-###"; List authoritySections = new List(5); long[] section = new long[] { 0, fileStream.Length }; string tag = ""; while(fileStream.Position < fileStream.Length) { char c = (char)fileStream.ReadByte(); if (tag == EndTag) { section[1] = fileStream.Position - EndTag.Length; authoritySections.Add(section); section = new long[] { fileStream.Position, fileStream.Length }; tag = ""; } else if (c == EndTag[tag.Length]) { tag += c; } else { tag = ""; } } authoritySections.Add(section); foreach (long[] foundSection in authoritySections) { Authority authority = new Authority(); authority.ReadValues(fileStream, foundSection[0], foundSection[1]); authorities.Add(authority); } } return new object[] { status, authorities }; } catch (FileNotFoundException e) { if (silent) { // If the app is running in silent, we have no vector to supply new data, so throw the exception throw e; } else { Console.WriteLine("File not found."); Console.WriteLine("Please enter the Domain: "); string domain = Console.ReadLine(); Console.WriteLine("Please enter the Authorisation Email Address: "); string email = Console.ReadLine(); Console.WriteLine("Please enter the Authorisation Key: "); string key = Console.ReadLine(); Console.WriteLine("Please enter the Zone ID: "); string zone = Console.ReadLine(); Console.WriteLine("Please enter the Dns ID: "); string dns = Console.ReadLine(); // Schedule an update to the credentials file (runs asynchronously as it may encounter io errors) if (currentTasks == null) { currentTasks = new Task[] { Task.Run(() => WriteFile(Environment.CurrentDirectory + "\\Credentials.dat", new string[] { Authority.auth_domain_tag + " = " + domain, Authority.auth_email_tag + " = " + email, Authority.auth_key_tag + " = " + key, Authority.auth_zone_tag + " = " + zone, Authority.auth_dns_tag + " = " + dns })) }; } else { currentTasks.Append(Task.Run(() => WriteFile(Environment.CurrentDirectory + "\\Credentials.dat", new string[] { Authority.auth_domain_tag + " = " + domain, Authority.auth_email_tag + " = " + email, Authority.auth_key_tag + " = " + key, Authority.auth_zone_tag + " = " + zone, Authority.auth_dns_tag + " = " + dns }))); } Authority authority = new Authority(); authority.DnsTag = authority.ReadBytes(new MemoryStream(Encoding.UTF8.GetBytes(dns)), 0, Encoding.UTF8.GetBytes(dns).Length); authority.DomainTag = authority.ReadBytes(new MemoryStream(Encoding.UTF8.GetBytes(domain)), 0, Encoding.UTF8.GetBytes(domain).Length); authority.EmailTag = authority.ReadBytes(new MemoryStream(Encoding.UTF8.GetBytes(email)), 0, Encoding.UTF8.GetBytes(email).Length); authority.KeyTag = authority.ReadBytes(new MemoryStream(Encoding.UTF8.GetBytes(key)), 0, Encoding.UTF8.GetBytes(key).Length); authority.ZoneTag = authority.ReadBytes(new MemoryStream(Encoding.UTF8.GetBytes(zone)), 0, Encoding.UTF8.GetBytes(zone).Length); List authorities = new List(1); return new object[] { status , authorities }; } } catch (Exception e) { Console.WriteLine(e); status = StatusCode.FileWriteFailiure; return new object[] { status }; } } public static void WriteFile(string path, string[] data) { bool success = false; int attemptLimit = 5; int attemptsCount = 0; while (!success && attemptsCount < attemptLimit) { try { FileStream fileStream = File.OpenWrite(path); foreach (var line in data) { fileStream.Write(Encoding.UTF8.GetBytes(line + Environment.NewLine), 0, Encoding.UTF8.GetBytes(line + Environment.NewLine).Length); } success = true; fileStream.Dispose(); } catch { attemptsCount++; Thread.Sleep(500); } } } } public class Authority : IDisposable { public const string auth_domain_tag = "X-AUTH-DOMAIN"; public const string auth_email_tag = "X-AUTH-EMAIL"; public const string auth_key_tag = "X-AUTH-KEY"; public const string auth_zone_tag = "X-AUTH-ZONE"; public const string auth_dns_tag = "X-AUTH-DNS"; public const string cloudfare_api_url = "https://api.cloudflare.com/client/v4/"; public ObfusctedBytes DomainTag { get; set; } public ObfusctedBytes EmailTag { get; set; } public ObfusctedBytes KeyTag { get; set; } public ObfusctedBytes ZoneTag { get; set; } public ObfusctedBytes DnsTag { get; set; } public void Dispose() { if (DomainTag != null) DomainTag.Dispose(); if (EmailTag != null) EmailTag.Dispose(); if (KeyTag != null) KeyTag.Dispose(); if (ZoneTag != null) ZoneTag.Dispose(); if (DnsTag != null) DnsTag.Dispose(); } public void ReadValues(Stream source, long start=-1, long end=-1) { if (start == -1) start = source.Position; else source.Position = start; if (end == -1) end = source.Length; // Start by finding the constraints of our pairs Dictionary keyValuePairs = new Dictionary(5); long[] constraints = new long[] {start, end}; long returnPos = -1; bool findData = false; string key = ""; while (source.Position < end) { int r = source.ReadByte(); if(findData) { while (r == ' ') { r = source.ReadByte(); } findData = false; long currentPos = source.Position; source.Position = start; for (int i = 0; i < currentPos; i++) { Console.Write((char)source.ReadByte()); } constraints[0] = source.Position; } else if(r == '=') { findData = true; } else if(r == '\r') { returnPos = source.Position; } else if(r == '\n') { constraints[1] = (returnPos > -1) ? returnPos : source.Position; returnPos = -1; keyValuePairs.Add(key, constraints); constraints = new long[] {source.Position, end }; key = ""; } // If this value would match else if(auth_domain_tag.Contains(key + (char)r) || auth_dns_tag.Contains(key + (char)r) || auth_email_tag.Contains(key + (char)r) || auth_key_tag.Contains(key + (char)r) || auth_zone_tag.Contains(key + (char)r)) { key += (char)r; } } keyValuePairs.Add(key, constraints); // Use the keys to create our objects foreach (var item in keyValuePairs) { switch (item.Key) { case auth_domain_tag: DomainTag = ReadBytes(source, item.Value[0], item.Value[1]); break; case auth_dns_tag: DnsTag = ReadBytes(source, item.Value[0], item.Value[1]); break; case auth_email_tag: EmailTag = ReadBytes(source, item.Value[0], item.Value[1]); break; case auth_key_tag: KeyTag = ReadBytes(source, item.Value[0], item.Value[1]); break; case auth_zone_tag: ZoneTag = ReadBytes(source, item.Value[0], item.Value[1]); break; default: break; } } } public ObfusctedBytes ReadBytes(Stream source, long start, long end) { source.Position = start-1; ObfusctedBytes bytes = new ObfusctedBytes(source.ReadByte()); while (source.Position < end-1) { bytes.LinkNext(source.ReadByte()); } return bytes; } /// /// An obfuscated byte helps mask the data in memory.
/// Based on a linked-list, data should not be expected to be contiguous.
/// Also includes simple bitwise motion to prevent passerby reading. ///
public class ObfusctedBytes : IDisposable { ObfusctedBytes nextByte = null; int position; private readonly int mask = 0b_1110_0011; byte[] values; public ObfusctedBytes(int value, int position=0) { Random prngle = new Random(); this.position = position; int byte1 = prngle.Next(0, 255); // start with a base number byte1 &= mask; // Zero our target bits byte1 |= ((value >> 5) << 2); // bitshift the value and mask it into the byte int byte2 = prngle.Next(0, 255); byte2 &= mask; byte2 |= value & ~mask; int byte3 = prngle.Next(0, 255); byte3 &= mask; byte3 |= (value & 0b_0000_0011) << 2; values = new byte[] { (byte)byte1,(byte)byte2,(byte)byte3}; } /// /// Evaluates the current byte, from the provided data /// /// an int representing the original data private int Evaluate() { int mask = 0b_1110_0011; return (((values[0] & ~mask) << 3) | (values[1] & ~mask) | ((values[2] >> 2) & 0b_0000_0011)); } public string EvaluateAll() { string result = string.Empty + (char)Evaluate(); ObfusctedBytes obfusctedBytes = this; while(obfusctedBytes.nextByte != null) { int t = obfusctedBytes.nextByte.Evaluate(); result += (char)t; obfusctedBytes = obfusctedBytes.nextByte; } return result; } public void LinkNext(int value) { int nextPosition = position; ObfusctedBytes nextElement = this; while (nextElement.nextByte != null) { nextElement = nextElement.nextByte; nextPosition++; } ObfusctedBytes nextByte = new ObfusctedBytes(value, ++nextPosition); nextElement.nextByte = nextByte; } public void Dispose() { if (nextByte != null) { nextByte.Dispose(); } for (int i = 0; i < values.Length; i++) { values[i] = 0; } values = null; nextByte = null; } } } }