diff --git a/Shodan.Net.sln b/Shodan.Net.sln
new file mode 100644
index 0000000..6da622d
--- /dev/null
+++ b/Shodan.Net.sln
@@ -0,0 +1,39 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 14
+VisualStudioVersion = 14.0.25123.0
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{060AE71C-AE05-4F14-8970-84A1AE49A562}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{18A95621-2E1B-48F0-9D38-D7B3513F31D3}"
+ ProjectSection(SolutionItems) = preProject
+ global.json = global.json
+ EndProjectSection
+EndProject
+Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Shodan.Net", "src\Shodan.Net\Shodan.Net.xproj", "{2AC1566F-7C77-499E-B9AE-1F029BD7F7E8}"
+EndProject
+Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Shodan.Net.UnitTests", "src\Shodan.Net.UnitTests\Shodan.Net.UnitTests.xproj", "{F83A8130-97B5-4AA2-810C-1D16A88799BD}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {2AC1566F-7C77-499E-B9AE-1F029BD7F7E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2AC1566F-7C77-499E-B9AE-1F029BD7F7E8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2AC1566F-7C77-499E-B9AE-1F029BD7F7E8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2AC1566F-7C77-499E-B9AE-1F029BD7F7E8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F83A8130-97B5-4AA2-810C-1D16A88799BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F83A8130-97B5-4AA2-810C-1D16A88799BD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F83A8130-97B5-4AA2-810C-1D16A88799BD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F83A8130-97B5-4AA2-810C-1D16A88799BD}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {2AC1566F-7C77-499E-B9AE-1F029BD7F7E8} = {060AE71C-AE05-4F14-8970-84A1AE49A562}
+ {F83A8130-97B5-4AA2-810C-1D16A88799BD} = {060AE71C-AE05-4F14-8970-84A1AE49A562}
+ EndGlobalSection
+EndGlobal
diff --git a/global.json b/global.json
new file mode 100644
index 0000000..b51e28b
--- /dev/null
+++ b/global.json
@@ -0,0 +1,6 @@
+{
+ "projects": [ "src", "test" ],
+ "sdk": {
+ "version": "1.0.0-preview1-002702"
+ }
+}
diff --git a/src/Shodan.Net.UnitTests/Class1.cs b/src/Shodan.Net.UnitTests/Class1.cs
new file mode 100644
index 0000000..8bed8bd
--- /dev/null
+++ b/src/Shodan.Net.UnitTests/Class1.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace Shodan.Net.UnitTests
+{
+ // This project can output the Class library as a NuGet Package.
+ // To enable this option, right-click on the project and select the Properties menu item. In the Build tab select "Produce outputs on build".
+ public class Class1
+ {
+ [Fact]
+ public async Task privateGetsPorts()
+
+ {
+ var client = new ShodanClient("9F0mxmNSaHbe0mYmefwoCZrChT2h0KzC");
+ var ports = await client.GetPortsAsync();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Shodan.Net.UnitTests/Properties/AssemblyInfo.cs b/src/Shodan.Net.UnitTests/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..061ef98
--- /dev/null
+++ b/src/Shodan.Net.UnitTests/Properties/AssemblyInfo.cs
@@ -0,0 +1,19 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("Shodan.Net.UnitTests")]
+[assembly: AssemblyTrademark("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("f83a8130-97b5-4aa2-810c-1d16a88799bd")]
diff --git a/src/Shodan.Net.UnitTests/Shodan.Net.UnitTests.xproj b/src/Shodan.Net.UnitTests/Shodan.Net.UnitTests.xproj
new file mode 100644
index 0000000..3674896
--- /dev/null
+++ b/src/Shodan.Net.UnitTests/Shodan.Net.UnitTests.xproj
@@ -0,0 +1,22 @@
+
+
+
+ 14.0
+ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
+
+
+
+ f83a8130-97b5-4aa2-810c-1d16a88799bd
+ Shodan.Net.UnitTests
+ .\obj
+ .\bin\
+ v4.5.2
+
+
+ 2.0
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Shodan.Net.UnitTests/project.json b/src/Shodan.Net.UnitTests/project.json
new file mode 100644
index 0000000..55d51cb
--- /dev/null
+++ b/src/Shodan.Net.UnitTests/project.json
@@ -0,0 +1,25 @@
+{
+ "version": "1.0.0-*",
+ "testRunner": "xunit",
+ "dependencies": {
+ "NETStandard.Library": "1.5.0-rc2-24027",
+ "xunit": "2.1.0",
+ "dotnet-test-xunit": "1.0.0-rc2-build10015",
+ "Shodan.Net": "1.0.0-*"
+ },
+
+ "frameworks": {
+ "netcoreapp1.0": {
+ "dependencies": {
+ "Microsoft.NETCore.App": {
+ "type": "platform",
+ "version": "1.0.0-rc2-3002702"
+ }
+ },
+ "imports": [
+ "dnxcore50",
+ "portable-net45+win8"
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Shodan.Net/Class1.cs b/src/Shodan.Net/Class1.cs
new file mode 100644
index 0000000..63e0f75
--- /dev/null
+++ b/src/Shodan.Net/Class1.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace Shodan.Net
+{
+ // This project can output the Class library as a NuGet Package.
+ // To enable this option, right-click on the project and select the Properties menu item. In the Build tab select "Produce outputs on build".
+ public class Class1
+ {
+ public Class1()
+ {
+ }
+ }
+}
diff --git a/src/Shodan.Net/IShodanAsyncClient.cs b/src/Shodan.Net/IShodanAsyncClient.cs
new file mode 100644
index 0000000..ba51b87
--- /dev/null
+++ b/src/Shodan.Net/IShodanAsyncClient.cs
@@ -0,0 +1,124 @@
+using Shodan.Net.Models;
+using Shodan.Net.Models.Options;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace Shodan.Net
+{
+ internal interface IShodanAsyncClient
+ {
+ ///
+ /// Returns all services that have been found on the given host IP.
+ ///
+ /// Host IP address
+ /// True if all historical banners should be returned (default: False)
+ /// True to only return the list of ports and the general host information, no banners. (default: False)
+ ///
+ Task GetHostAsync(string Ip, bool history = false, bool minify = false);
+
+ ///
+ /// This method returns a list of port numbers that the crawlers are looking for.
+ ///
+ ///
+ Task> GetPortsAsync();
+
+ ///
+ /// This method returns an object containing all the protocols that can be used when launching an Internet scan.
+ ///
+ ///
+ Task> GetProtocolsAsync();
+
+ ///
+ /// Use this method to request Shodan to crawl a network
+ /// Requirements: This method uses API scan credits: 1 IP consumes 1 scan credit. You must have a paid API plan (either one-time payment or subscription) in order to use this method
+ ///
+ ///
+ Task RequstScanAsync(string ips);
+
+ ///
+ ///
+ ///
+ /// The port that Shodan should crawl the Internet for.
+ /// The name of the protocol that should be used to interrogate the port. See /shodan/protocols for a list of supported protocols.
+ ///
+ Task RequestInternetPortScanAsync(int port, string protocol);
+
+ ///
+ /// Check the progress of a previously submitted scan request
+ ///
+ /// the unique scan ID that was returned by /shodan/scan
+ ///
+ Task GetScanStatusAsync(string id);
+
+ ///
+ /// This method returns an object containing all the services that the Shodan crawlers look at. It can also be used as a quick and practical way to resolve a port number to the name of a service
+ ///
+ ///
+ Task> GetServicesAsync();
+
+ ///
+ /// Use this method to obtain a list of search queries that users have saved in Shodan.
+ ///
+ /// Page number to iterate over results; each page contains 10 items
+ /// Sort the list based on a property. Possible values are: votes, timestamp
+ /// Whether to sort the list in ascending or descending order. Possible values are: asc, desc
+ ///
+ Task GetQueriesAsync(int? page = null, SortOptions? options = null, OrderOption? order = null);
+
+ ///
+ /// Use this method to search the directory of search queries that users have saved in Shodan.
+ ///
+ /// What to search for in the directory of saved search queries.
+ /// Page number to iterate over results; each page contains 10 items
+ ///
+ Task SearchQueriesAsync(string query, int? page = null);
+
+ ///
+ /// Use this method to obtain a list of popular tags for the saved search queries in Shodan.
+ ///
+ /// The number of tags to return
+ ///
+ Task GetTagsAsync(int size = 10);
+
+ ///
+ /// Returns information about the Shodan account linked to this API key.
+ ///
+ ///
+ Task GetProfileAsync();
+
+ ///
+ /// Look up the IP address for the provided list of hostnames.
+ ///
+ /// Comma-separated list of hostnames; example "google.com,bing.com"
+ ///
+ Task> DnsLookupAsync(string hostnames);
+
+ ///
+ /// Look up the hostnames that have been defined for the given list of IP addresses
+ ///
+ /// Comma-separated list of IP addresses; example "74.125.227.230,204.79.197.200"
+ ///
+ Task>> ReverseLookupAsync(string ips);
+
+ ///
+ /// Get your current IP address as seen from the Internet.
+ ///
+ ///
+ Task GetMyIpAsync();
+
+ ///
+ /// Returns information about the API plan belonging to the given API key.
+ ///
+ ///
+ Task GetApiStatusAsync();
+
+ ///
+ /// Calculates a honeypot probability score rangin from 0 (not a honeypot) to 1.0 (is a honeypot).
+ ///
+ ///
+ ///
+ Task Experimental_GetHoneyPotScoreAsync(string ip);
+ }
+}
\ No newline at end of file
diff --git a/src/Shodan.Net/IShodanClient.cs b/src/Shodan.Net/IShodanClient.cs
new file mode 100644
index 0000000..e522897
--- /dev/null
+++ b/src/Shodan.Net/IShodanClient.cs
@@ -0,0 +1,124 @@
+using Shodan.Net.Models;
+using Shodan.Net.Models.Options;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace Shodan.Net
+{
+ public interface IShodanClient
+ {
+ ///
+ /// Returns all services that have been found on the given host IP.
+ ///
+ /// Host IP address
+ /// True if all historical banners should be returned (default: False)
+ /// True to only return the list of ports and the general host information, no banners. (default: False)
+ ///
+ Host GetHost(string Ip, bool history = false, bool minify = false);
+
+ ///
+ /// This method returns a list of port numbers that the crawlers are looking for.
+ ///
+ ///
+ List GetPorts();
+
+ ///
+ /// This method returns an object containing all the protocols that can be used when launching an Internet scan.
+ ///
+ ///
+ Dictionary GetProtocols();
+
+ ///
+ /// Use this method to request Shodan to crawl a network
+ /// Requirements: This method uses API scan credits: 1 IP consumes 1 scan credit. You must have a paid API plan (either one-time payment or subscription) in order to use this method
+ ///
+ ///
+ ScanResult RequstScan(string ips);
+
+ ///
+ ///
+ ///
+ /// The port that Shodan should crawl the Internet for.
+ /// The name of the protocol that should be used to interrogate the port. See /shodan/protocols for a list of supported protocols.
+ ///
+ ScanPortResult RequestPortScan(int port, string protocol);
+
+ ///
+ /// Check the progress of a previously submitted scan request
+ ///
+ /// the unique scan ID that was returned by /shodan/scan
+ ///
+ ScanStatus GetScanStatus(string id);
+
+ ///
+ /// This method returns an object containing all the services that the Shodan crawlers look at. It can also be used as a quick and practical way to resolve a port number to the name of a service
+ ///
+ ///
+ Dictionary GetServices();
+
+ ///
+ /// Use this method to obtain a list of search queries that users have saved in Shodan.
+ ///
+ /// Page number to iterate over results; each page contains 10 items
+ /// Sort the list based on a property. Possible values are: votes, timestamp
+ /// Whether to sort the list in ascending or descending order. Possible values are: asc, desc
+ ///
+ SearchQueries GetQueries(int? page = null, SortOptions? options = null, OrderOption? order = null);
+
+ ///
+ /// Use this method to search the directory of search queries that users have saved in Shodan.
+ ///
+ /// What to search for in the directory of saved search queries.
+ /// Page number to iterate over results; each page contains 10 items
+ ///
+ SearchQueries GetQuerySearches(string query, int? page = null);
+
+ ///
+ /// Use this method to obtain a list of popular tags for the saved search queries in Shodan.
+ ///
+ /// The number of tags to return
+ ///
+ TagResult GetTags(int size = 10);
+
+ ///
+ /// Returns information about the Shodan account linked to this API key.
+ ///
+ ///
+ Profile GetProfile();
+
+ ///
+ /// Look up the IP address for the provided list of hostnames.
+ ///
+ /// Comma-separated list of hostnames; example "google.com,bing.com"
+ ///
+ Dictionary DnsLookup(string hostnames);
+
+ ///
+ /// Look up the hostnames that have been defined for the given list of IP addresses
+ ///
+ /// Comma-separated list of IP addresses; example "74.125.227.230,204.79.197.200"
+ ///
+ Dictionary> ReverseLookup(string ips);
+
+ ///
+ /// Get your current IP address as seen from the Internet.
+ ///
+ ///
+ string GetMyIp();
+
+ ///
+ /// Returns information about the API plan belonging to the given API key.
+ ///
+ ///
+ ApiStatus GetApiStatus();
+
+ ///
+ /// Calculates a honeypot probability score rangin from 0 (not a honeypot) to 1.0 (is a honeypot).
+ ///
+ ///
+ ///
+ double Experimental_GetHoneyPotScore(string ip);
+ }
+}
\ No newline at end of file
diff --git a/src/Shodan.Net/Models/ApiStatus.cs b/src/Shodan.Net/Models/ApiStatus.cs
new file mode 100644
index 0000000..a9f0b8d
--- /dev/null
+++ b/src/Shodan.Net/Models/ApiStatus.cs
@@ -0,0 +1,30 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.Serialization;
+using System.Threading.Tasks;
+
+namespace Shodan.Net.Models
+{
+ [DataContract]
+ public class ApiStatus
+ {
+ [DataMember(Name = "query_credits")]
+ public int QueryCredits { get; set; }
+
+ [DataMember(Name = "scan_credits")]
+ public int ScanCredits { get; set; }
+
+ [DataMember(Name = "telnet")]
+ public bool Telnet { get; set; }
+
+ [DataMember(Name = "plan")]
+ public string Plan { get; set; }
+
+ [DataMember(Name = "https")]
+ public bool Https { get; set; }
+
+ [DataMember(Name = "unlocked")]
+ public bool Unlocked { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Shodan.Net/Models/Banner.cs b/src/Shodan.Net/Models/Banner.cs
new file mode 100644
index 0000000..76af578
--- /dev/null
+++ b/src/Shodan.Net/Models/Banner.cs
@@ -0,0 +1,139 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.Serialization;
+using System.Threading.Tasks;
+
+namespace Shodan.Net.Models
+{
+ [DataContract]
+ public class Banner
+ {
+ ///
+ /// The timestamp for when the banner was fetched from the device in the UTC timezone.
+ ///
+ [DataMember(Name = "timestamp")]
+ public DateTime Timestamp { get; set; }
+
+ ///
+ /// Either "udp" or "tcp" to indicate which IP transport protocol was used to fetch the information
+ ///
+ [DataMember(Name = "transport")]
+ public string Transport { get; set; }
+
+ ///
+ /// An array of strings containing all of the hostnames that have been assigned to the IP address for this device.
+ ///
+ [DataMember(Name = "hostnames")]
+ public IList Hostnames { get; set; }
+
+ ///
+ /// The name of the organization that is assigned the IP space for this device.
+ ///
+ [DataMember(Name = "org")]
+ public string Org { get; set; }
+
+ [DataMember(Name = "guid")]
+ public string Guid { get; set; }
+
+ ///
+ /// Contains the banner information for the service.
+ ///
+ [DataMember(Name = "data")]
+ public string Data { get; set; }
+
+ ///
+ /// The port number that the service is operating on
+ ///
+ [DataMember(Name = "port")]
+ public int Port { get; set; }
+
+ ///
+ /// The ISP that is providing the organization with the IP space for this device. Consider this the "parent" of the organization in terms of IP ownership.
+ ///
+ [DataMember(Name = "isp")]
+ public string Isp { get; set; }
+
+ ///
+ /// The autonomous system number (ex. "AS4837"
+ ///
+ [DataMember(Name = "asn")]
+ public string Asn { get; set; }
+
+ [DataMember(Name = "location")]
+ public Location Location { get; set; }
+
+ ///
+ /// The IP address of the host as an integer
+ ///
+ [DataMember(Name = "ip")]
+ public int? Ip { get; set; }
+
+ ///
+ /// The IPv6 address of the host as a string. If this is present then the "ip" and "ip_str" fields wont be.
+ ///
+ [DataMember(Name = "ip")]
+ public string Ipv6 { get; set; }
+
+ ///
+ /// ] An array of strings containing the top-level domains for the hostnames of the device. This is a utility property in case you want to filter by TLD instead of subdomain. It is smart enough to handle global TLDs with several dots in the domain (ex. "co.uk")
+ ///
+ [DataMember(Name = "domains")]
+ public IList Domains { get; set; }
+
+ ///
+ /// The IP address of the host as a string
+ ///
+ [DataMember(Name = "ip_str")]
+ public string IpStr { get; set; }
+
+ ///
+ /// The operating system that powers the device.
+ ///
+ [DataMember(Name = "os")]
+ public object Os { get; set; }
+
+ ///
+ /// Contains experimental and supplemental data for the service. This can include the SSL certificate, robots.txt and other raw information that hasn't yet been formalized into the Banner Specification.
+ ///
+ [DataMember(Name = "opts", IsRequired = false)]
+ public dynamic Opts { get; set; }
+
+ #region Optional Properties
+
+ ///
+ /// The number of minutes that the device has been online.
+ ///
+ [DataMember(Name = "uptime", IsRequired = false)]
+ public int? Uptime { get; set; }
+
+ [DataMember(Name = "link", IsRequired = false)]
+ public string Link { get; set; }
+
+ [DataMember(Name = "title", IsRequired = false)]
+ public string Title { get; set; }
+
+ [DataMember(Name = "html", IsRequired = false)]
+ public string Html { get; set; }
+
+ [DataMember(Name = "product", IsRequired = false)]
+ public string Product { get; set; }
+
+ [DataMember(Name = "version", IsRequired = false)]
+ public string Version { get; set; }
+
+ [DataMember(Name = "devicetype", IsRequired = false)]
+ public string DeviceType { get; set; }
+
+ [DataMember(Name = "info", IsRequired = false)]
+ public string Info { get; set; }
+
+ [DataMember(Name = "cpe", IsRequired = false)]
+ public string Cpe { get; set; }
+
+ [DataMember(Name = "ssl", IsRequired = false)]
+ public SslProperties Ssl { get; set; }
+
+ #endregion Optional Properties
+ }
+}
\ No newline at end of file
diff --git a/src/Shodan.Net/Models/Host.cs b/src/Shodan.Net/Models/Host.cs
new file mode 100644
index 0000000..0752d33
--- /dev/null
+++ b/src/Shodan.Net/Models/Host.cs
@@ -0,0 +1,39 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.Serialization;
+using System.Threading.Tasks;
+
+namespace Shodan.Net.Models
+{
+ [DataContract]
+ public class Host
+ {
+ [DataMember(Name = "region_code")]
+ public string RegionCode { get; set; }
+
+ [DataMember(Name = "ip")]
+ public string Ip { get; set; }
+
+ [DataMember(Name = "area_code")]
+ public string AreaCode { get; set; }
+
+ [DataMember(Name = "country_names")]
+ public string CountryName { get; set; }
+
+ [DataMember(Name = "hostnames")]
+ public List Hostnames { get; set; }
+
+ [DataMember(Name = "postal_code")]
+ public string PostalCode { get; set; }
+
+ [DataMember(Name = "dma_code")]
+ public string DmaCode { get; set; }
+
+ [DataMember(Name = "country_code")]
+ public string CountryCode { get; set; }
+
+ [DataMember(Name = "data")]
+ public List Data { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Shodan.Net/Models/Location.cs b/src/Shodan.Net/Models/Location.cs
new file mode 100644
index 0000000..f35479c
--- /dev/null
+++ b/src/Shodan.Net/Models/Location.cs
@@ -0,0 +1,78 @@
+using System.Runtime.Serialization;
+
+namespace Shodan.Net.Models
+{
+ [DataContract]
+ public class Location
+ {
+ ///
+ /// An object containing all of the location information for the device.
+ ///
+ public Location()
+ {
+ }
+
+ ///
+ /// The 3-letter country code for the device location.
+ ///
+
+ [DataMember(Name = "country_code3")]
+ public string CountryCode3 { get; set; }
+
+ ///
+ /// The name of the city where the device is located.
+ ///
+ [DataMember(Name = "city")]
+ public string City { get; set; }
+
+ ///
+ /// The postal code for the device's location.
+ ///
+ [DataMember(Name = "postal_code")]
+ public string PostalCode { get; set; }
+
+ ///
+ /// The longitude for the geolocation of the device.
+ ///
+ [DataMember(Name = "longitude")]
+ public double Longitude { get; set; }
+
+ ///
+ /// The 2-letter country code for the device location.
+ ///
+
+ [DataMember(Name = "country_code")]
+ public string CountryCode { get; set; }
+
+ ///
+ /// The latitude for the geolocation of the device
+ ///
+
+ [DataMember(Name = "latitude")]
+ public double Latitude { get; set; }
+
+ ///
+ /// The name of the country where the device is located.
+ ///
+ [DataMember(Name = "country_name")]
+ public string CountryName { get; set; }
+
+ ///
+ /// The area code for the device's location. Only available for the US.
+ ///
+ [DataMember(Name = "area_code")]
+ public int AreaCode { get; set; }
+
+ ///
+ /// The designated market area code for the area where the device is located. Only available for the US.
+ ///
+ [DataMember(Name = "dma_code")]
+ public int? DmaCode { get; set; }
+
+ ///
+ /// The name of the region where the device is located.
+ ///
+ [DataMember(Name = "region_code")]
+ public string RegionCode { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Shodan.Net/Models/Options/OrderOption.cs b/src/Shodan.Net/Models/Options/OrderOption.cs
new file mode 100644
index 0000000..8ac5eca
--- /dev/null
+++ b/src/Shodan.Net/Models/Options/OrderOption.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace Shodan.Net.Models.Options
+{
+ public enum OrderOption
+ {
+ asc,
+ desc
+ }
+}
\ No newline at end of file
diff --git a/src/Shodan.Net/Models/Options/RequestType.cs b/src/Shodan.Net/Models/Options/RequestType.cs
new file mode 100644
index 0000000..b955516
--- /dev/null
+++ b/src/Shodan.Net/Models/Options/RequestType.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace Shodan.Net.Models.Options
+{
+ public enum RequestType
+ {
+ GET = 0,
+ PUT = 1,
+ POST = 2,
+ DELETE = 4
+ }
+}
\ No newline at end of file
diff --git a/src/Shodan.Net/Models/Options/SortOptions.cs b/src/Shodan.Net/Models/Options/SortOptions.cs
new file mode 100644
index 0000000..1cc059f
--- /dev/null
+++ b/src/Shodan.Net/Models/Options/SortOptions.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace Shodan.Net.Models.Options
+{
+ public enum SortOptions
+ {
+ votes,
+ timestamp
+ }
+}
\ No newline at end of file
diff --git a/src/Shodan.Net/Models/Profile.cs b/src/Shodan.Net/Models/Profile.cs
new file mode 100644
index 0000000..4785bea
--- /dev/null
+++ b/src/Shodan.Net/Models/Profile.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.Serialization;
+using System.Threading.Tasks;
+
+namespace Shodan.Net.Models
+{
+ [DataContract]
+ public class Profile
+ {
+ [DataMember(Name = "member")]
+ public bool Member { get; set; }
+
+ [DataMember(Name = "credits")]
+ public int Credits { get; set; }
+
+ [DataMember(Name = "display_name")]
+ public string DisplayName { get; set; }
+
+ [DataMember(Name = "created")]
+ public DateTime Created { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Shodan.Net/Models/ScanPortResult.cs b/src/Shodan.Net/Models/ScanPortResult.cs
new file mode 100644
index 0000000..c88d0b8
--- /dev/null
+++ b/src/Shodan.Net/Models/ScanPortResult.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.Serialization;
+using System.Threading.Tasks;
+
+namespace Shodan.Net.Models
+{
+ [DataContract]
+ public class ScanPortResult
+ {
+ [DataMember(Name = "id")]
+ public string Id { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Shodan.Net/Models/ScanResult.cs b/src/Shodan.Net/Models/ScanResult.cs
new file mode 100644
index 0000000..90dbca7
--- /dev/null
+++ b/src/Shodan.Net/Models/ScanResult.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.Serialization;
+using System.Threading.Tasks;
+
+namespace Shodan.Net.Models
+{
+ [DataContract]
+ public class ScanResult
+ {
+ [DataMember(Name = "id")]
+ public string Id { get; set; }
+
+ [DataMember(Name = "count")]
+ public int Count { get; set; }
+
+ [DataMember(Name = "credits_left")]
+ public int CreditsLeft { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Shodan.Net/Models/ScanStatus.cs b/src/Shodan.Net/Models/ScanStatus.cs
new file mode 100644
index 0000000..c9ee9c7
--- /dev/null
+++ b/src/Shodan.Net/Models/ScanStatus.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.Serialization;
+using System.Threading.Tasks;
+
+namespace Shodan.Net.Models
+{
+ [DataContract]
+ public class ScanStatus
+ {
+ [DataMember(Name = "id")]
+ public string Id { get; set; }
+
+ [DataMember(Name = "count")]
+ public int Count { get; set; }
+
+ [DataMember(Name = "status")]
+ public StatusEnum? Status { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Shodan.Net/Models/SearchQueries.cs b/src/Shodan.Net/Models/SearchQueries.cs
new file mode 100644
index 0000000..5bd2329
--- /dev/null
+++ b/src/Shodan.Net/Models/SearchQueries.cs
@@ -0,0 +1,40 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.Serialization;
+using System.Threading.Tasks;
+
+namespace Shodan.Net.Models
+{
+ [DataContract]
+ public class Match
+ {
+ [DataMember(Name = "votes")]
+ public int Votes { get; set; }
+
+ [DataMember(Name = "description")]
+ public string Description { get; set; }
+
+ [DataMember(Name = "title")]
+ public string Title { get; set; }
+
+ [DataMember(Name = "timestamp")]
+ public DateTime Timestamp { get; set; }
+
+ [DataMember(Name = "tags")]
+ public IList Tags { get; set; }
+
+ [DataMember(Name = "query")]
+ public string Query { get; set; }
+ }
+
+ [DataContract]
+ public class SearchQueries
+ {
+ [DataMember(Name = "total")]
+ public int Total { get; set; }
+
+ [DataMember(Name = "matches")]
+ public IList Matches { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Shodan.Net/Models/SslProperties.cs b/src/Shodan.Net/Models/SslProperties.cs
new file mode 100644
index 0000000..3873fba
--- /dev/null
+++ b/src/Shodan.Net/Models/SslProperties.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.Serialization;
+using System.Threading.Tasks;
+
+namespace Shodan.Net.Models
+{
+ [DataContract]
+ public class SslProperties
+ {
+ ///
+ /// The parsed certificate properties that includes information such as when it was issued, the SSL extensions, the issuer, subject etc.
+ ///
+ [DataMember(Name = "cert")]
+ public dynamic Cert { get; set; }
+
+ ///
+ /// Preferred cipher for the SSL connection
+ ///
+ [DataMember(Name = "ciper")]
+ public dynamic Ciper { get; set; }
+
+ ///
+ /// An array of certificates, where each string is a PEM-encoded SSL certificate. This includes the user SSL certificate up to its root certificate.
+ ///
+ [DataMember(Name = "chain")]
+ public IList Chain { get; set; }
+
+ ///
+ /// The Diffie-Hellman parameters if available: "prime", "public_key", "bits", "generator" and an optional "fingerprint" if we know which program generated these parameters.
+ ///
+ [DataMember(Name = "dhparams")]
+ public dynamic DhParams { get; set; }
+
+ ///
+ /// A list of SSL versions that are supported by the server. If a version isnt supported the value is prefixed with a "-". Example: ["TLSv1", "-SSLv2"] means that the server supports TLSv1 but doesnt support SSLv2.
+ ///
+ [DataMember(Name = "versions")]
+ public IList Versions { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Shodan.Net/Models/StatusEnum.cs b/src/Shodan.Net/Models/StatusEnum.cs
new file mode 100644
index 0000000..c2d7ae1
--- /dev/null
+++ b/src/Shodan.Net/Models/StatusEnum.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace Shodan.Net.Models
+{
+ public enum StatusEnum
+ {
+ SUBMITTING,
+ QUEUE,
+ PROCESSING,
+ DONE
+ }
+}
\ No newline at end of file
diff --git a/src/Shodan.Net/Models/TagResult.cs b/src/Shodan.Net/Models/TagResult.cs
new file mode 100644
index 0000000..c3a0b0c
--- /dev/null
+++ b/src/Shodan.Net/Models/TagResult.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.Serialization;
+using System.Threading.Tasks;
+
+namespace Shodan.Net.Models
+{
+ [DataContract]
+ public class TagResult
+ {
+ [DataMember(Name = "total")]
+ public int Total { get; set; }
+
+ [DataMember(Name = "matches")]
+ public IList Matches { get; set; }
+
+ [DataContract]
+ public class Match
+ {
+ [DataMember(Name = "value")]
+ public string Value { get; set; }
+
+ [DataMember(Name = "count")]
+ public int Count { get; set; }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Shodan.Net/Properties/AssemblyInfo.cs b/src/Shodan.Net/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..36f7aac
--- /dev/null
+++ b/src/Shodan.Net/Properties/AssemblyInfo.cs
@@ -0,0 +1,19 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("Shodan.Net")]
+[assembly: AssemblyTrademark("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("2ac1566f-7c77-499e-b9ae-1f029bd7f7e8")]
diff --git a/src/Shodan.Net/QueryGenerator.cs b/src/Shodan.Net/QueryGenerator.cs
new file mode 100644
index 0000000..01cf26b
--- /dev/null
+++ b/src/Shodan.Net/QueryGenerator.cs
@@ -0,0 +1,205 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Shodan.Net
+{
+ public class QueryGenerator
+ {
+ internal Dictionary queryData { get; set; }
+ private HashSet CalledMethods = new HashSet();
+
+ public QueryGenerator Before(DateTime time)
+ {
+ queryData.Add("before", time.ToString("dd/MM/yyyy"));
+ return this;
+ }
+
+ ///
+ /// Only show results that were collected after the given date
+ ///
+ ///
+ ///
+ public QueryGenerator After(DateTime time)
+ {
+ queryData.Add("after", time.ToString("dd/MM/yyyy"));
+ return this;
+ }
+
+ ///
+ /// The Autonomous System Number that identifies the network the device is on.
+ ///
+ ///
+ ///
+ public QueryGenerator WithAsn(string asn)
+ {
+ queryData.Add("asn", asn);
+ return this;
+ }
+
+ public QueryGenerator InCity(string city)
+ {
+ queryData.Add("city", city);
+ return this;
+ }
+
+ public QueryGenerator InCountry(string country)
+ {
+ queryData.Add("country", country);
+ return this;
+ }
+
+ ///
+ /// If "true" only show results that have a screenshot available.
+ ///
+ ///
+ ///
+ public QueryGenerator HasScreenshot(bool hasScreenshot = true)
+ {
+ queryData.Add("has_screenshot", hasScreenshot.ToString());
+ return this;
+ }
+
+ public QueryGenerator WithHostname(string hostname)
+ {
+ queryData.Add("hostname", hostname);
+ return this;
+ }
+
+ public QueryGenerator WithHtml(string html)
+ {
+ queryData.Add("html", html);
+ return this;
+ }
+
+ public QueryGenerator WithIsp(string isp)
+ {
+ queryData.Add("isp", isp);
+ return this;
+ }
+
+ public QueryGenerator WithNet(string net)
+ {
+ queryData.Add("net", net);
+ return this;
+ }
+
+ public QueryGenerator WithOrg(string org)
+ {
+ queryData.Add("org", org);
+ return this;
+ }
+
+ public QueryGenerator WithOs(string os)
+ {
+ queryData.Add("os", os);
+ return this;
+ }
+
+ public QueryGenerator Withport(string port)
+ {
+ queryData.Add("port", port);
+ return this;
+ }
+
+ public QueryGenerator With_postal(string postal)
+ {
+ queryData.Add("postal", postal);
+ return this;
+ }
+
+ public QueryGenerator With_product(string product)
+ {
+ queryData.Add("product", product);
+ return this;
+ }
+
+ public QueryGenerator With_state(string state)
+ {
+ queryData.Add("state", state);
+ return this;
+ }
+
+ public QueryGenerator With_title(string title)
+ {
+ queryData.Add("title", title);
+ return this;
+ }
+
+ public QueryGenerator With_version(string version)
+ {
+ queryData.Add("version", version);
+ return this;
+ }
+
+ public QueryGenerator With_Bitcoinip(string bitcoinip)
+ {
+ queryData.Add("bitcoin.ip", bitcoinip);
+ return this;
+ }
+
+ public QueryGenerator With_Bitcoinip_count(string bitcoinip_count)
+ {
+ queryData.Add("bitcoin.ip_count", bitcoinip_count);
+ return this;
+ }
+
+ public QueryGenerator WithBitcoinport(string bitcoinport)
+ {
+ queryData.Add("bitcoin.port", bitcoinport);
+ return this;
+ }
+
+ public QueryGenerator WithBitcoinversion(string bitcoinversion)
+ {
+ queryData.Add("bitcoin.version", bitcoinversion);
+ return this;
+ }
+
+ public QueryGenerator WithNtpip(string ntpip)
+ {
+ queryData.Add("ntp.ip", ntpip);
+ return this;
+ }
+
+ public QueryGenerator WithNtpip_count(string ntpip_count)
+ {
+ queryData.Add("ntp.ip_count", ntpip_count);
+ return this;
+ }
+
+ public QueryGenerator WithNtpmore(string ntpmore)
+ {
+ queryData.Add("ntp.more", ntpmore);
+ return this;
+ }
+
+ public QueryGenerator WithNtpport(string ntpport)
+ {
+ queryData.Add("ntp.port", ntpport);
+ return this;
+ }
+
+ public SearchQuery Generate(string searchText)
+ {
+ var sb = new StringBuilder(searchText);
+ sb.Append(" ");
+ foreach(var item in queryData)
+ {
+ sb.Append($"{item.Key}:{item.Value}");
+ }
+ return new SearchQuery(sb.ToString());
+ }
+
+ private void EnsureMethodNotCalled(string methodName)
+ {
+ if(CalledMethods.Contains(methodName))
+ {
+ throw new ShodanException($"{methodName} cannot be called twice");
+ }
+ CalledMethods.Add(methodName);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Shodan.Net/SearchQuery.cs b/src/Shodan.Net/SearchQuery.cs
new file mode 100644
index 0000000..7909d2e
--- /dev/null
+++ b/src/Shodan.Net/SearchQuery.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace Shodan.Net
+{
+ public class SearchQuery
+ {
+ internal SearchQuery(string builtQuery)
+ {
+ this.Query = builtQuery;
+ }
+
+ internal string Query { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Shodan.Net/SearchQueryGenerator.cs b/src/Shodan.Net/SearchQueryGenerator.cs
new file mode 100644
index 0000000..461dd46
--- /dev/null
+++ b/src/Shodan.Net/SearchQueryGenerator.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace Shodan.Net
+{
+ public class SearchQueryGenerator
+ {
+ public QueryGenerator WithQuery() => new QueryGenerator();
+ }
+}
\ No newline at end of file
diff --git a/src/Shodan.Net/Shodan.Net.xproj b/src/Shodan.Net/Shodan.Net.xproj
new file mode 100644
index 0000000..b0fa893
--- /dev/null
+++ b/src/Shodan.Net/Shodan.Net.xproj
@@ -0,0 +1,21 @@
+
+
+
+ 14.0
+ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
+
+
+
+
+ 2ac1566f-7c77-499e-b9ae-1f029bd7f7e8
+ Shodan.Net
+ .\obj
+ .\bin\
+ v4.5.2
+
+
+
+ 2.0
+
+
+
diff --git a/src/Shodan.Net/ShodanClient.cs b/src/Shodan.Net/ShodanClient.cs
new file mode 100644
index 0000000..5053116
--- /dev/null
+++ b/src/Shodan.Net/ShodanClient.cs
@@ -0,0 +1,326 @@
+using Newtonsoft.Json;
+using Shodan.Net.Models;
+using Shodan.Net.Models.Options;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Threading.Tasks;
+
+namespace Shodan.Net
+{
+ public class ShodanClient : IShodanAsyncClient
+ {
+ private readonly string apikey;
+ private const string BasePath = "https://api.shodan.io";
+
+ //todo error handle!!!
+ //todo:
+ /*
+ /shodan/host/count
+ /shodan/host/search
+ /shodan/host/search/tokens
+ */
+
+ public ShodanClient(string apikey)
+ {
+ if(string.IsNullOrWhiteSpace(apikey))
+ {
+ throw new ArgumentNullException(nameof(apikey));
+ }
+ this.apikey = apikey;
+ }
+
+ ///
+ /// Look up the IP address for the provided list of hostnames.
+ ///
+ /// Comma-separated list of hostnames; example "google.com,bing.com"
+ ///
+ public Task> DnsLookupAsync(string hostnames)
+ {
+ if(string.IsNullOrWhiteSpace(hostnames))
+ {
+ throw new ArgumentNullException(hostnames);
+ }
+ var url = new Uri($"{BasePath}/dns/resolve?hostnames={hostnames}&key={this.apikey}");
+ return MakeRequestAsync>(url);
+ }
+
+ ///
+ /// Calculates a honeypot probability score ranging from 0 (not a honeypot) to 1.0 (is a honeypot).
+ ///
+ ///
+ ///
+ public async Task Experimental_GetHoneyPotScoreAsync(string ip)
+ {
+ if(string.IsNullOrWhiteSpace(ip))
+ {
+ throw new ArgumentNullException(nameof(ip));
+ }
+ var url = new Uri($"{BasePath}/labs/honeyscore/{ip}?key={apikey}");
+ var result = await MakeRequestAsync(url);
+ double resultParsed;
+ if(!double.TryParse(result, out resultParsed))
+ {
+ throw new ShodanException($"honeypot score returned with {result} failed to parse to double");
+ }
+ return resultParsed;
+ }
+
+ ///
+ /// Returns information about the API plan belonging to the given API key.
+ ///
+ ///
+ public Task GetApiStatusAsync()
+ {
+ var url = new Uri($"{BasePath}/api-info?key={apikey}");
+ return MakeRequestAsync(url);
+ }
+
+ ///
+ /// Returns all services that have been found on the given host IP.
+ ///
+ /// Host IP address
+ /// True if all historical banners should be returned (default: False)
+ /// True to only return the list of ports and the general host information, no banners. (default: False)
+ ///
+ public Task GetHostAsync(string Ip, bool history = false, bool minify = false)
+ {
+ if(string.IsNullOrWhiteSpace(Ip))
+ {
+ throw new ArgumentNullException(nameof(Ip));
+ }
+ var builder = new UriBuilder($"{BasePath}/shodan/host/{Ip}")
+ {
+ Query = $"key={this.apikey}&history={history.ToString()}&minify={minify.ToString()}"
+ };
+
+ return MakeRequestAsync(builder.Uri);
+ }
+
+ ///
+ /// Get your current IP address as seen from the Internet.
+ ///
+ ///
+ public Task GetMyIpAsync()
+ {
+ var url = new Uri($"{BasePath}/tools/myip?key={this.apikey}");
+ return MakeRequestAsync(url);
+ }
+
+ ///
+ /// This method returns a list of port numbers that the crawlers are looking for.
+ ///
+ ///
+ public Task> GetPortsAsync()
+ {
+ var builder = new Uri($"{BasePath}/shodan/ports?key={this.apikey}");
+ return MakeRequestAsync>(builder);
+ }
+
+ private async static Task MakeRequestAsync(Uri url, HttpContent content = null, RequestType requstType = RequestType.GET)
+ where T : class
+ {
+ if(requstType != RequestType.GET && content == null)
+ {
+ throw new ShodanException($"Request type {requstType} requires content");
+ }
+ if(requstType == RequestType.DELETE || requstType == RequestType.PUT)
+ {
+ throw new NotImplementedException("Put and Delete requests have not been implemented properly");
+ }
+ using(var client = new HttpClient())
+ {
+ HttpResponseMessage connection = null;
+ if(requstType == RequestType.GET)
+ {
+ connection = await client.GetAsync(url);
+ }
+ else if(requstType == RequestType.POST)
+ {
+ connection = await client.PostAsync(url, content);
+ }
+
+ var statusCode = (int)connection.StatusCode;
+ if(statusCode != 200 && statusCode != 201 && statusCode == 202)
+ {
+ //todo error handle
+ return null;
+ }
+ var readResult = await connection.Content.ReadAsStringAsync();
+ if(typeof(T) == typeof(string))
+ {
+ return readResult as T;
+ }
+ return JsonConvert.DeserializeObject(readResult);
+ }
+ }
+
+ ///
+ /// Returns information about the Shodan account linked to this API key.
+ ///
+ ///
+ public Task GetProfileAsync()
+ {
+ var url = new Uri($"{BasePath}/account/profile?key={apikey}");
+ return MakeRequestAsync(url);
+ }
+
+ ///
+ /// This method returns an object containing all the protocols that can be used when launching an Internet scan.
+ ///
+ ///
+ public Task> GetProtocolsAsync()
+ {
+ var url = new Uri($"{BasePath}/shodan/protocols?key={this.apikey}");
+ return MakeRequestAsync>(url);
+ }
+
+ ///
+ /// Use this method to obtain a list of search queries that users have saved in Shodan.
+ ///
+ /// Page number to iterate over results; each page contains 10 items
+ /// Sort the list based on a property. Possible values are: votes, timestamp
+ /// Whether to sort the list in ascending or descending order. Possible values are: asc, desc
+ ///
+ public Task GetQueriesAsync(int? page = null, SortOptions? sort = null, OrderOption? order = null)
+ {
+ var url = new UriBuilder($"{BasePath}/shodan/query")
+ {
+ Query = $"key={apikey}"
+ };
+ if(sort.HasValue)
+ {
+ var sortName = Enum.GetName(typeof(SortOptions), sort.Value);
+ url.Query = $"{url.Query}&sort={sortName}";
+ }
+ if(order.HasValue)
+ {
+ var orderName = Enum.GetName(typeof(OrderOption), order.Value);
+ url.Query = $"{url.Query}&order={orderName}";
+ }
+ return MakeRequestAsync(url.Uri);
+ }
+
+ ///
+ /// Use this method to search the directory of search queries that users have saved in Shodan.
+ ///
+ /// What to search for in the directory of saved search queries.
+ /// Page number to iterate over results; each page contains 10 items
+ ///
+ public Task SearchQueriesAsync(string query, int? page = null)
+ {
+ if(string.IsNullOrWhiteSpace(query))
+ {
+ throw new ArgumentNullException(query);
+ }
+ var url = new UriBuilder($"{BasePath}/shodan/query/search")
+ {
+ Query = $"key={apikey}&query={query}"
+ };
+ if(page != null)
+ {
+ url.Query = $"{url.Query}&page={page}";
+ }
+ return MakeRequestAsync(url.Uri);
+ }
+
+ ///
+ /// Check the progress of a previously submitted scan request
+ ///
+ /// the unique scan ID that was returned by
+ ///
+ public Task GetScanStatusAsync(string id)
+ {
+ if(string.IsNullOrWhiteSpace(id))
+ {
+ throw new ArgumentNullException(nameof(id));
+ }
+ var url = new Uri($"{BasePath}/shodan/scan/{id}");
+ return MakeRequestAsync(url);
+ }
+
+ ///
+ /// This method returns an object containing all the services that the Shodan crawlers look at. It can also be used as a quick and practical way to resolve a port number to the name of a service
+ ///
+ ///
+ public Task> GetServicesAsync()
+ {
+ var url = new Uri($"{BasePath}/shodan/services?key={this.apikey}");
+ return MakeRequestAsync>(url);
+ }
+
+ ///
+ /// Use this method to obtain a list of popular tags for the saved search queries in Shodan.
+ ///
+ /// The number of tags to return
+ ///
+ public Task GetTagsAsync(int size = 10)
+ {
+ var url = new UriBuilder($"{BasePath}/shodan/query/tags")
+ {
+ Query = $"key={apikey}&size={size}"
+ };
+ return MakeRequestAsync(url.Uri);
+ }
+
+ ///
+ /// Use this method to request Shodan to crawl the Internet for a specific port.
+ /// This method is restricted to security researchers and companies with a Shodan Data license. To apply for access to this method as a researcher, please email jmath@shodan.io with information about your project. Access is restricted to prevent abuse.
+ ///
+ /// The port that Shodan should crawl the Internet for.
+ /// The name of the protocol that should be used to interrogate the port. See for a list of supported protocols.
+ ///
+ public Task RequestInternetPortScanAsync(int port, string protocol)
+ {
+ var url = new Uri($"{BasePath}/shodan/scan/internet?key={this.apikey}");
+ using(var data = new FormUrlEncodedContent(new List>() {
+ new KeyValuePair("port", port.ToString()),
+ new KeyValuePair("protocol", protocol)
+ }))
+ {
+ return MakeRequestAsync(url, data, RequestType.POST);
+ }
+ }
+
+ ///
+ /// Use this method to request Shodan to crawl a network
+ /// Requirements: This method uses API scan credits: 1 IP consumes 1 scan credit. You must have a paid API plan (either one-time payment or subscription) in order to use this method
+ ///
+ ///
+ ///
+ public Task RequstScanAsync(string ips)
+ {
+ if(string.IsNullOrWhiteSpace(ips))
+ {
+ throw new ArgumentNullException(nameof(ips));
+ }
+ if(!ips.Split(',').Any())
+ {
+ throw new ArgumentOutOfRangeException($"{ips} must have one valid record");
+ }
+ var url = new Uri($"{BasePath}/shodan/scan?key={this.apikey}");
+ using(var data = new FormUrlEncodedContent(new KeyValuePair[] { new KeyValuePair("ips", ips) }))
+ {
+ return MakeRequestAsync(url, data, RequestType.POST);
+ }
+ }
+
+ ///
+ /// Look up the hostnames that have been defined for the given list of IP addresses
+ ///
+ /// Comma-separated list of IP addresses; example "74.125.227.230,204.79.197.200"
+ ///
+ public Task>> ReverseLookupAsync(string ips)
+ {
+ if(string.IsNullOrWhiteSpace(ips))
+ {
+ throw new ArgumentNullException(ips);
+ }
+ var url = new Uri($"{BasePath}/dns/reverse?ips={ips}&key={this.apikey}");
+ return MakeRequestAsync>>(url);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Shodan.Net/ShodanException.cs b/src/Shodan.Net/ShodanException.cs
new file mode 100644
index 0000000..d498acc
--- /dev/null
+++ b/src/Shodan.Net/ShodanException.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace Shodan.Net
+{
+ public class ShodanException : Exception
+ {
+ public ShodanException()
+ {
+ }
+
+ public ShodanException(string message) : base(message)
+ {
+ }
+
+ public ShodanException(string message, Exception innerException) : base(message, innerException)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Shodan.Net/project.json b/src/Shodan.Net/project.json
new file mode 100644
index 0000000..95d8e59
--- /dev/null
+++ b/src/Shodan.Net/project.json
@@ -0,0 +1,17 @@
+{
+ "version": "1.0.0-*",
+
+ "dependencies": {
+ "NETStandard.Library": "1.5.0-rc2-24027",
+ "System.Runtime.Serialization.Primitives": "4.1.1-rc2-24027",
+ "System.Dynamic.Runtime": "4.0.11-rc2-24027",
+ "Newtonsoft.Json": "8.0.3",
+ "System.Diagnostics.TraceSource": "4.0.0-rc2-24027"
+ },
+
+ "frameworks": {
+ "netstandard1.5": {
+ "imports": "dnxcore50"
+ }
+ }
+}
\ No newline at end of file