basic api done

This commit is contained in:
Tommy Parnell
2016-05-21 13:43:30 -04:00
parent 33017ca9a3
commit 82f0c1add2
33 changed files with 3112 additions and 255 deletions

1
.gitignore vendored
View File

@@ -21,6 +21,7 @@
bld/
[Bb]in/
[Oo]bj/
output/
# Visual Studio 2015 cache/options directory
.vs/

45
Readme.md Normal file
View File

@@ -0,0 +1,45 @@
Visit the official Shodan API documentation at:
[https://developer.shodan.io](https://developer.shodan.io)
## Installation
`install-package Shodan.Net`
## Getting started
You need to have an Api key. Get your [api key here](http://www.shodanhq.com/api_doc).
Create a shodan client. Note that ShodanClient inerhits from IDisposable, so you should wrap it in a using, or make sure it will be disposed. Shodan client is thread safe, so you should be able to keep 1 object around for many requests.
`var client = new Shodan.Net.ShodanClient("myapiKey");`
Now just query away.
```csharp
await client.GetPortsAsync();
await client.GetHostAsync("172.1.1.0");
await client.GetMyIpAsync();
```
## Searching
Searching shodan requires you to build up queries, and facets to make it easier we have used a generator pattern to produce queries.
```csharp
await client.SearchHosts(
query: a => a.Withcity("boston")
.Withcountry("usa")
.Before(DateTime.Now.AddDays(-5)),
facet: b => b.WithAsn()
);
```

View File

@@ -7,7 +7,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{060AE71C-AE0
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{18A95621-2E1B-48F0-9D38-D7B3513F31D3}"
ProjectSection(SolutionItems) = preProject
.gitignore = .gitignore
global.json = global.json
Readme.md = Readme.md
EndProjectSection
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Shodan.Net", "src\Shodan.Net\Shodan.Net.xproj", "{2AC1566F-7C77-499E-B9AE-1F029BD7F7E8}"

2425
doxy.config Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -14,8 +14,14 @@ namespace Shodan.Net.UnitTests
public async Task privateGetsPorts()
{
var client = new ShodanClient("9F0mxmNSaHbe0mYmefwoCZrChT2h0KzC");
var ports = await client.GetPortsAsync();
var client = new ShodanClient("");
var ports = await client.SearchHosts(
query: a => a.Withcity("boston")
.Withcountry("usa")
.Before(DateTime.Now.AddDays(-5)),
facet: b => b.WithAsn()
);
}
}
}

View File

@@ -1,16 +0,0 @@
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()
{
}
}
}

View File

@@ -1,124 +0,0 @@
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
{
/// <summary>
/// Returns all services that have been found on the given host IP.
/// </summary>
/// <param name="Ip">Host IP address</param>
/// <param name="history">True if all historical banners should be returned (default: False) </param>
/// <param name="minify">True to only return the list of ports and the general host information, no banners. (default: False) </param>
/// <returns></returns>
Host GetHost(string Ip, bool history = false, bool minify = false);
/// <summary>
/// This method returns a list of port numbers that the crawlers are looking for.
/// </summary>
/// <returns></returns>
List<int> GetPorts();
/// <summary>
/// This method returns an object containing all the protocols that can be used when launching an Internet scan.
/// </summary>
/// <returns></returns>
Dictionary<string, string> GetProtocols();
/// <summary>
/// Use this method to request Shodan to crawl a network
/// <strong>Requirements:</strong> 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
/// </summary>
/// <returns></returns>
ScanResult RequstScan(string ips);
/// <summary>
///
/// </summary>
/// <param name="port"> The port that Shodan should crawl the Internet for. </param>
/// <param name="protocol">The name of the protocol that should be used to interrogate the port. See /shodan/protocols for a list of supported protocols. </param>
/// <returns></returns>
ScanPortResult RequestPortScan(int port, string protocol);
/// <summary>
/// Check the progress of a previously submitted scan request
/// </summary>
/// <param name="id">the unique scan ID that was returned by /shodan/scan</param>
/// <returns></returns>
ScanStatus GetScanStatus(string id);
/// <summary>
/// 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
/// </summary>
/// <returns></returns>
Dictionary<string, string> GetServices();
/// <summary>
/// Use this method to obtain a list of search queries that users have saved in Shodan.
/// </summary>
/// <param name="page"> Page number to iterate over results; each page contains 10 items </param>
/// <param name="options"> Sort the list based on a property. Possible values are: votes, timestamp </param>
/// <param name="order">Whether to sort the list in ascending or descending order. Possible values are: asc, desc </param>
/// <returns></returns>
SearchQueries GetQueries(int? page = null, SortOptions? options = null, OrderOption? order = null);
/// <summary>
/// Use this method to search the directory of search queries that users have saved in Shodan.
/// </summary>
/// <param name="query"> What to search for in the directory of saved search queries. </param>
/// <param name="page">Page number to iterate over results; each page contains 10 items </param>
/// <returns></returns>
SearchQueries GetQuerySearches(string query, int? page = null);
/// <summary>
/// Use this method to obtain a list of popular tags for the saved search queries in Shodan.
/// </summary>
/// <param name="size">The number of tags to return </param>
/// <returns></returns>
TagResult GetTags(int size = 10);
/// <summary>
/// Returns information about the Shodan account linked to this API key.
/// </summary>
/// <returns></returns>
Profile GetProfile();
/// <summary>
/// Look up the IP address for the provided list of hostnames.
/// </summary>
/// <param name="hostnames">Comma-separated list of hostnames; example "google.com,bing.com" </param>
/// <returns></returns>
Dictionary<string, string> DnsLookup(string hostnames);
/// <summary>
/// Look up the hostnames that have been defined for the given list of IP addresses
/// </summary>
/// <param name="ips">Comma-separated list of IP addresses; example "74.125.227.230,204.79.197.200"</param>
/// <returns></returns>
Dictionary<string, List<string>> ReverseLookup(string ips);
/// <summary>
/// Get your current IP address as seen from the Internet.
/// </summary>
/// <returns></returns>
string GetMyIp();
/// <summary>
/// Returns information about the API plan belonging to the given API key.
/// </summary>
/// <returns></returns>
ApiStatus GetApiStatus();
/// <summary>
/// Calculates a honeypot probability score rangin from 0 (not a honeypot) to 1.0 (is a honeypot).
/// </summary>
/// <param name="ip"></param>
/// <returns></returns>
double Experimental_GetHoneyPotScore(string ip);
}
}

View File

@@ -6,6 +6,9 @@ using System.Threading.Tasks;
namespace Shodan.Net.Models
{
/// <summary>
/// Returns information about the API plan belonging to the given API key.
/// </summary>
[DataContract]
public class ApiStatus
{

View File

@@ -6,6 +6,9 @@ using System.Threading.Tasks;
namespace Shodan.Net.Models
{
/// <summary>
/// Represents basic return data
/// </summary>
[DataContract]
public class Banner
{

View File

@@ -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
{
/// <summary>
/// Represents return facet data
/// </summary>
[DataContract]
public class Facet
{
[DataMember(Name = "count")]
public int? Count { get; set; }
[DataMember(Name = "value")]
public string Value { get; set; }
}
}

View File

@@ -6,6 +6,9 @@ using System.Threading.Tasks;
namespace Shodan.Net.Models
{
/// <summary>
/// Represents return data for querying hosts
/// </summary>
[DataContract]
public class Host
{
@@ -35,5 +38,23 @@ namespace Shodan.Net.Models
[DataMember(Name = "data")]
public List<Banner> Data { get; set; }
[DataMember(Name = "city")]
public string City { get; set; }
[DataMember(Name = "longitude")]
public double Longitude { get; set; }
[DataMember(Name = "country_code3")]
public string CountryCode3 { get; set; }
[DataMember(Name = "latitude")]
public double Latitude { get; set; }
[DataMember(Name = "os")]
public string Os { get; set; }
[DataMember(Name = "ports")]
public IList<int> Ports { get; set; }
}
}

View File

@@ -2,6 +2,9 @@
namespace Shodan.Net.Models
{
/// <summary>
/// Basic location data returned by shodan
/// </summary>
[DataContract]
public class Location
{

View File

@@ -3,8 +3,11 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Shodan.Net.Models.Options
namespace Shodan.Net
{
/// <summary>
/// Represents an order of either ascending or descending
/// </summary>
public enum OrderOption
{
asc,

View File

@@ -3,8 +3,11 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Shodan.Net.Models.Options
namespace Shodan.Net
{
/// <summary>
/// Represents an option to sort
/// </summary>
public enum SortOptions
{
votes,

View File

@@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Shodan.Net.Models
namespace Shodan.Net.Models.Options
{
public enum StatusEnum
{

View File

@@ -6,6 +6,9 @@ using System.Threading.Tasks;
namespace Shodan.Net.Models
{
/// <summary>
/// Represents data about your profile
/// </summary>
[DataContract]
public class Profile
{

View File

@@ -6,6 +6,9 @@ using System.Threading.Tasks;
namespace Shodan.Net.Models
{
/// <summary>
/// result of <see cref="ShodanClient.RequestInternetPortScanAsync(int, string)"/>
/// </summary>
[DataContract]
public class ScanPortResult
{

View File

@@ -6,6 +6,9 @@ using System.Threading.Tasks;
namespace Shodan.Net.Models
{
/// <summary>
/// result of <see cref="ShodanClient.RequstScanAsync(string)"/>
/// </summary>
[DataContract]
public class ScanResult
{

View File

@@ -1,4 +1,5 @@
using System;
using Shodan.Net.Models.Options;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
@@ -6,6 +7,9 @@ using System.Threading.Tasks;
namespace Shodan.Net.Models
{
/// <summary>
/// Result of <see cref="ShodanClient.GetScanStatusAsync(string)"/>
/// </summary>
[DataContract]
public class ScanStatus
{

View File

@@ -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
{
/// <summary>
/// result of <see cref="ShodanClient.SearchHosts(SearchQuery, FacetQuery, int, bool)"/> and <see cref="ShodanClient.SearchHostsCount(SearchQuery, FacetQuery)"/>
/// </summary>
[DataContract]
public class SearchHostResults
{
[DataMember(Name = "matches")]
public List<Banner> Matches { get; set; }
[DataMember(Name = "facets")]
public Dictionary<string, Facet> Facets { get; set; }
[DataMember(Name = "total")]
public int? Total { get; set; }
}
}

View File

@@ -28,6 +28,9 @@ namespace Shodan.Net.Models
public string Query { get; set; }
}
/// <summary>
/// Result of <see cref="ShodanClient.GetQueriesAsync(int?, SortOptions?, OrderOption?)"/> and <seealso cref="ShodanClient.SearchQueriesAsync(string, int?)"/>
/// </summary>
[DataContract]
public class SearchQueries
{

View File

@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Threading.Tasks;
namespace Shodan.Net.Models
{
[DataContract]
public class Attributes
{
[DataMember(Name = "ports")]
public IList<int> Ports { get; set; }
}
[DataContract]
public class SearchTokens
{
[DataMember(Name = "attributes")]
public Attributes Attributes { get; set; }
[DataMember(Name = "errors")]
public IList<dynamic> Errors { get; set; }
[DataMember(Name = "string")]
public string String { get; set; }
[DataMember(Name = "filters")]
public IList<dynamic> Filters { get; set; }
}
}

View File

@@ -6,6 +6,9 @@ using System.Threading.Tasks;
namespace Shodan.Net.Models
{
/// <summary>
/// result of <see cref="ShodanClient.GetTagsAsync(int)"/>
/// </summary>
[DataContract]
public class TagResult
{

View File

@@ -0,0 +1,18 @@
using Shodan.Net.Models.Options;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
namespace Shodan.Net
{
/// <summary>
/// sane wrapper of http, and simple abstraction layer for unit testing
/// </summary>
public interface IRequstHandler : IDisposable
{
Task<T> MakeRequestAsync<T>(Uri url, HttpContent content = null, RequestType requstType = RequestType.GET)
where T : class;
}
}

View File

@@ -0,0 +1,85 @@
using Newtonsoft.Json;
using Shodan.Net.Models.Options;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
namespace Shodan.Net
{
/// <summary>
/// sane wrapper of http, and simple abstraction layer for unit testing
/// </summary>
public class RequestHandler : IRequstHandler
{
private HttpClient client { get; set; }
public async Task<T> MakeRequestAsync<T>(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");
}
HttpResponseMessage connection = null;
if(requstType == RequestType.GET)
{
connection = await client.GetAsync(url);
}
else if(requstType == RequestType.POST)
{
connection = await client.PostAsync(url, content);
}
//todo error handle based on user input
connection.EnsureSuccessStatusCode();
//var statusCode = (int)connection.StatusCode;
//if(statusCode != 200 && statusCode != 201 && statusCode == 202)
//{
// System.Diagnostics.Trace.TraceError("Error calling shodan API, Status code is not 200" + JsonConvert.SerializeObject(connection.s))
// //todo error handle
// return null;
//}
var readResult = await connection.Content.ReadAsStringAsync();
if(typeof(T) == typeof(string))
{
return readResult as T;
}
return JsonConvert.DeserializeObject<T>(readResult);
}
#region IDisposable Support
private bool disposedValue = false; // To detect redundant calls
protected virtual void Dispose(bool disposing)
{
if(!disposedValue)
{
if(disposing)
{
client.Dispose();
}
disposedValue = true;
}
}
// This code added to correctly implement the disposable pattern.
public void Dispose()
{
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
Dispose(true);
// TODO: uncomment the following line if the finalizer is overridden above.
// GC.SuppressFinalize(this);
}
#endregion IDisposable Support
}
}

View File

@@ -2,6 +2,11 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
/*! \mainpage Shodan.Net
* Shodan.net is a simple c# implementation of the shodan api
* Shodan.net is missing the streams api, but it will come soon. Most of the action occurs in ShodanClient.cs
*/
// 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.

View File

@@ -0,0 +1,215 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Shodan.Net
{
public class FacetGenerator
{
private Dictionary<string, int?> queryData = new Dictionary<string, int?>();
internal FacetGenerator()
{
}
internal string GenerateFacets()
{
var data = queryData.Select(a => a.Value.HasValue ? $"{a.Key}:{a.Value}" : a.Key);
return string.Join(",", data);
}
public FacetGenerator WithAsn(int? count = null)
{
queryData.Add("asn", count);
return this;
}
public FacetGenerator WithCity(int? count = null)
{
queryData.Add("city", count);
return this;
}
public FacetGenerator WithCountry(int? count = null)
{
queryData.Add("country", count);
return this;
}
public FacetGenerator WithDevice(int? count = null)
{
queryData.Add("device", count);
return this;
}
public FacetGenerator WithDomain(int? count = null)
{
queryData.Add("domain", count);
return this;
}
public FacetGenerator WithGeocluster(int? count = null)
{
queryData.Add("geocluster", count);
return this;
}
public FacetGenerator HasScreenshot(int? count = null)
{
queryData.Add("has_screenshot", count);
return this;
}
public FacetGenerator WithIsp(int? count = null)
{
queryData.Add("isp", count);
return this;
}
public FacetGenerator WithLink(int? count = null)
{
queryData.Add("link", count);
return this;
}
public FacetGenerator WithOrg(int? count = null)
{
queryData.Add("org", count);
return this;
}
public FacetGenerator WithOs(int? count = null)
{
queryData.Add("os", count);
return this;
}
public FacetGenerator WithPort(int? count = null)
{
queryData.Add("port", count);
return this;
}
public FacetGenerator WithPostal(int? count = null)
{
queryData.Add("postal", count);
return this;
}
public FacetGenerator WithState(int? count = null)
{
queryData.Add("state", count);
return this;
}
public FacetGenerator WithTimestamp_day(int? count = null)
{
queryData.Add("timestamp_day", count);
return this;
}
public FacetGenerator WithTimestamp_month(int? count = null)
{
queryData.Add("timestamp_month", count);
return this;
}
public FacetGenerator WithTimestamp_year(int? count = null)
{
queryData.Add("timestamp_year", count);
return this;
}
public FacetGenerator WithUptime(int? count = null)
{
queryData.Add("uptime", count);
return this;
}
public FacetGenerator WithVersion(int? count = null)
{
queryData.Add("version", count);
return this;
}
public FacetGenerator WithBitcoin_ip(int? count = null)
{
queryData.Add("bitcoin.ip", count);
return this;
}
public FacetGenerator WithBitcoin_ip_count(int? count = null)
{
queryData.Add("bitcoin.ip_count", count);
return this;
}
public FacetGenerator WithBitcoin_port(int? count = null)
{
queryData.Add("bitcoin.port", count);
return this;
}
public FacetGenerator WithBitcoin_user_agent(int? count = null)
{
queryData.Add("bitcoin.user_agent", count);
return this;
}
public FacetGenerator WithBitcoin_version(int? count = null)
{
queryData.Add("bitcoin.version", count);
return this;
}
public FacetGenerator WithNtp_ip(int? count = null)
{
queryData.Add("ntp.ip", count);
return this;
}
public FacetGenerator WithNtp_ip_count(int? count = null)
{
queryData.Add("ntp.ip_count", count);
return this;
}
public FacetGenerator WithNtp_more(int? count = null)
{
queryData.Add("ntp.more", count);
return this;
}
public FacetGenerator WithNtp_port(int? count = null)
{
queryData.Add("ntp.port", count);
return this;
}
public FacetGenerator WithSsh_cipher(int? count = null)
{
queryData.Add("ssh.cipher", count);
return this;
}
public FacetGenerator WithSsh_fingerprint(int? count = null)
{
queryData.Add("ssh.fingerprint", count);
return this;
}
public FacetGenerator WithSsh_mac(int? count = null)
{
queryData.Add("ssh.mac", count);
return this;
}
public FacetGenerator WithSsh_type(int? count = null)
{
queryData.Add("ssh.type", count);
return this;
}
}
}

View File

@@ -10,6 +10,11 @@ namespace Shodan.Net
{
internal Dictionary<string, string> queryData { get; set; }
private HashSet<string> CalledMethods = new HashSet<string>();
private string searchText = string.Empty;
internal QueryGenerator()
{
}
public QueryGenerator Before(DateTime time)
{
@@ -39,13 +44,13 @@ namespace Shodan.Net
return this;
}
public QueryGenerator InCity(string city)
public QueryGenerator Withcity(string city)
{
queryData.Add("city", city);
return this;
}
public QueryGenerator InCountry(string country)
public QueryGenerator Withcountry(string country)
{
queryData.Add("country", country);
return this;
@@ -182,24 +187,28 @@ namespace Shodan.Net
return this;
}
public SearchQuery Generate(string searchText)
public QueryGenerator WithText(string searchText)
{
if(!string.IsNullOrWhiteSpace(this.searchText))
{
throw new ShodanException("Method cannot be called twice");
}
this.searchText = searchText;
return this;
}
internal string Generate()
{
var sb = new StringBuilder(searchText);
sb.Append(" ");
if(!string.IsNullOrWhiteSpace(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);
return sb.ToString();
}
}
}

View File

@@ -1,17 +0,0 @@
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; }
}
}

View File

@@ -1,12 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Shodan.Net
{
public class SearchQueryGenerator
{
public QueryGenerator WithQuery() => new QueryGenerator();
}
}

View File

@@ -11,18 +11,16 @@ using System.Threading.Tasks;
namespace Shodan.Net
{
public class ShodanClient : IShodanAsyncClient
/// <summary>
/// Main mechanism to talk to the shodan api. This is what you should use to interact with the api
/// </summary>
public class ShodanClient : IShodanAsyncClient, IDisposable
{
private readonly string apikey;
private const string BasePath = "https://api.shodan.io";
private IRequstHandler RequestHandler = new RequestHandler();
//todo error handle!!!
//todo:
/*
/shodan/host/count
/shodan/host/search
/shodan/host/search/tokens
*/
public ShodanClient(string apikey)
{
@@ -33,6 +31,12 @@ namespace Shodan.Net
this.apikey = apikey;
}
internal ShodanClient(string apikey, IRequstHandler requestHandler)
: this(apikey)
{
RequestHandler = requestHandler;
}
/// <summary>
/// Look up the IP address for the provided list of hostnames.
/// </summary>
@@ -45,7 +49,86 @@ namespace Shodan.Net
throw new ArgumentNullException(hostnames);
}
var url = new Uri($"{BasePath}/dns/resolve?hostnames={hostnames}&key={this.apikey}");
return MakeRequestAsync<Dictionary<string, string>>(url);
return RequestHandler.MakeRequestAsync<Dictionary<string, string>>(url);
}
/// <summary>
/// Search Shodan using the same query syntax as the website and use facets to get summary information for different properties.
/// This method may use API query credits depending on usage. If any of the following criteria are met, your account will be deducated 1 query credit:
/// 1. The search query contains a filter.
/// 2. Accessing results past the 1st page using the "page". For every 100 results past the 1st page 1 query credit is deducted.
/// </summary>
/// <param name="query">lambda to generate a query. Shodan search query. The provided string is used to search the database of banners in Shodan, with the additional option to provide filters inside the search query using a "filter:value" format.</param>
/// <param name="facet">static class to define facets.</param>
/// <param name="page">The page number to page through results 100 at a time (default: 1) </param>
/// <param name="minify">True or False; whether or not to truncate some of the larger fields (default: True) </param>
/// <returns></returns>
public Task<SearchHostResults> SearchHosts(Action<QueryGenerator> query, Action<FacetGenerator> facet = null, int page = 1, bool minify = true)
{
if(query == null)
{
throw new ArgumentNullException(nameof(query));
}
var queryGenerator = new QueryGenerator();
query.Invoke(queryGenerator);
var queryResult = queryGenerator.Generate();
var url = new UriBuilder($"{BasePath}/shodan/host/search")
{
Query = $"key={apikey}&query={queryResult}&minify={minify.ToString()}"
};
if(facet != null)
{
var facetGenerator = new FacetGenerator();
facet.Invoke(facetGenerator);
url.Query = $"{url.Query}&facets={facetGenerator.GenerateFacets()}";
}
if(page > 1)
{
url.Query = $"{url.Query}&page={page}";
}
return RequestHandler.MakeRequestAsync<SearchHostResults>(url.Uri);
}
/// <summary>
/// This method behaves identical to <see cref="SearchHosts(SearchQuery, FacetQuery, int, bool)"/>" with the only difference that this method does not return any host results, it only returns the total number of results that matched the query and any facet information that was requested. As a result this method does not consume query credits.
/// </summary>
/// <param name="query"></param>
/// <param name="facet"></param>
/// <returns></returns>
public Task<SearchHostResults> SearchHostsCount(Action<QueryGenerator> query, Action<FacetGenerator> facet = null)
{
if(query == null)
{
throw new ArgumentNullException(nameof(query));
}
var queryGenObj = new QueryGenerator();
query.Invoke(queryGenObj);
var url = new UriBuilder($"{BasePath}/shodan/host/count")
{
Query = $"key={apikey}&query={queryGenObj.Generate()}"
};
if(facet != null)
{
var facetGenObj = new FacetGenerator();
facet.Invoke(facetGenObj);
url.Query = $"{url.Query}&facets={facetGenObj.GenerateFacets()}";
}
return RequestHandler.MakeRequestAsync<SearchHostResults>(url.Uri);
}
public Task<SearchTokens> SearchTokens(Action<QueryGenerator> query)
{
if(query == null)
{
throw new ArgumentNullException(nameof(query));
}
var queryObj = new QueryGenerator();
query.Invoke(queryObj);
var url = new Uri($"{BasePath}/shodan/host/search/tokens?key={apikey}&query={queryObj.Generate()}");
return RequestHandler.MakeRequestAsync<SearchTokens>(url);
}
/// <summary>
@@ -60,7 +143,7 @@ namespace Shodan.Net
throw new ArgumentNullException(nameof(ip));
}
var url = new Uri($"{BasePath}/labs/honeyscore/{ip}?key={apikey}");
var result = await MakeRequestAsync<string>(url);
var result = await RequestHandler.MakeRequestAsync<string>(url);
double resultParsed;
if(!double.TryParse(result, out resultParsed))
{
@@ -76,7 +159,7 @@ namespace Shodan.Net
public Task<ApiStatus> GetApiStatusAsync()
{
var url = new Uri($"{BasePath}/api-info?key={apikey}");
return MakeRequestAsync<ApiStatus>(url);
return RequestHandler.MakeRequestAsync<ApiStatus>(url);
}
/// <summary>
@@ -97,7 +180,7 @@ namespace Shodan.Net
Query = $"key={this.apikey}&history={history.ToString()}&minify={minify.ToString()}"
};
return MakeRequestAsync<Host>(builder.Uri);
return RequestHandler.MakeRequestAsync<Host>(builder.Uri);
}
/// <summary>
@@ -107,7 +190,7 @@ namespace Shodan.Net
public Task<string> GetMyIpAsync()
{
var url = new Uri($"{BasePath}/tools/myip?key={this.apikey}");
return MakeRequestAsync<string>(url);
return RequestHandler.MakeRequestAsync<string>(url);
}
/// <summary>
@@ -117,45 +200,7 @@ namespace Shodan.Net
public Task<List<int>> GetPortsAsync()
{
var builder = new Uri($"{BasePath}/shodan/ports?key={this.apikey}");
return MakeRequestAsync<List<int>>(builder);
}
private async static Task<T> MakeRequestAsync<T>(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<T>(readResult);
}
return RequestHandler.MakeRequestAsync<List<int>>(builder);
}
/// <summary>
@@ -165,7 +210,7 @@ namespace Shodan.Net
public Task<Profile> GetProfileAsync()
{
var url = new Uri($"{BasePath}/account/profile?key={apikey}");
return MakeRequestAsync<Profile>(url);
return RequestHandler.MakeRequestAsync<Profile>(url);
}
/// <summary>
@@ -175,7 +220,7 @@ namespace Shodan.Net
public Task<Dictionary<string, string>> GetProtocolsAsync()
{
var url = new Uri($"{BasePath}/shodan/protocols?key={this.apikey}");
return MakeRequestAsync<Dictionary<string, string>>(url);
return RequestHandler.MakeRequestAsync<Dictionary<string, string>>(url);
}
/// <summary>
@@ -201,7 +246,7 @@ namespace Shodan.Net
var orderName = Enum.GetName(typeof(OrderOption), order.Value);
url.Query = $"{url.Query}&order={orderName}";
}
return MakeRequestAsync<SearchQueries>(url.Uri);
return RequestHandler.MakeRequestAsync<SearchQueries>(url.Uri);
}
/// <summary>
@@ -224,7 +269,7 @@ namespace Shodan.Net
{
url.Query = $"{url.Query}&page={page}";
}
return MakeRequestAsync<SearchQueries>(url.Uri);
return RequestHandler.MakeRequestAsync<SearchQueries>(url.Uri);
}
/// <summary>
@@ -239,7 +284,7 @@ namespace Shodan.Net
throw new ArgumentNullException(nameof(id));
}
var url = new Uri($"{BasePath}/shodan/scan/{id}");
return MakeRequestAsync<ScanStatus>(url);
return RequestHandler.MakeRequestAsync<ScanStatus>(url);
}
/// <summary>
@@ -249,7 +294,7 @@ namespace Shodan.Net
public Task<Dictionary<string, string>> GetServicesAsync()
{
var url = new Uri($"{BasePath}/shodan/services?key={this.apikey}");
return MakeRequestAsync<Dictionary<string, string>>(url);
return RequestHandler.MakeRequestAsync<Dictionary<string, string>>(url);
}
/// <summary>
@@ -263,7 +308,7 @@ namespace Shodan.Net
{
Query = $"key={apikey}&size={size}"
};
return MakeRequestAsync<TagResult>(url.Uri);
return RequestHandler.MakeRequestAsync<TagResult>(url.Uri);
}
/// <summary>
@@ -281,7 +326,7 @@ namespace Shodan.Net
new KeyValuePair<string, string>("protocol", protocol)
}))
{
return MakeRequestAsync<ScanPortResult>(url, data, RequestType.POST);
return RequestHandler.MakeRequestAsync<ScanPortResult>(url, data, RequestType.POST);
}
}
@@ -304,7 +349,7 @@ namespace Shodan.Net
var url = new Uri($"{BasePath}/shodan/scan?key={this.apikey}");
using(var data = new FormUrlEncodedContent(new KeyValuePair<string, string>[] { new KeyValuePair<string, string>("ips", ips) }))
{
return MakeRequestAsync<ScanResult>(url, data, RequestType.POST);
return RequestHandler.MakeRequestAsync<ScanResult>(url, data, RequestType.POST);
}
}
@@ -320,7 +365,35 @@ namespace Shodan.Net
throw new ArgumentNullException(ips);
}
var url = new Uri($"{BasePath}/dns/reverse?ips={ips}&key={this.apikey}");
return MakeRequestAsync<Dictionary<string, List<string>>>(url);
return RequestHandler.MakeRequestAsync<Dictionary<string, List<string>>>(url);
}
#region IDisposable Support
private bool disposedValue = false; // To detect redundant calls
protected virtual void Dispose(bool disposing)
{
if(!disposedValue)
{
if(disposing)
{
RequestHandler.Dispose();
}
disposedValue = true;
}
}
// This code added to correctly implement the disposable pattern.
public void Dispose()
{
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
Dispose(true);
// TODO: uncomment the following line if the finalizer is overridden above.
// GC.SuppressFinalize(this);
}
#endregion IDisposable Support
}
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
namespace Shodan.Net

View File

@@ -1,17 +1,27 @@
{
{
"version": "1.0.0-*",
"authors": [ "Tommy Parnell" ],
"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"
"Newtonsoft.Json": "8.0.3"
},
"frameworks": {
"netstandard1.5": {
"imports": "dnxcore50"
}
},
"buildOptions": {
"xmlDoc": false
},
"packOptions": {
"owners": [ "Tommy Parnell" ],
"summary": "Wrapper over the shodan.io api",
"repository": {
"type": "git",
"url": "https://github.com/tparnell8/Shodan.Net"
},
"tags": [ "shodan.io" ]
}
}