File size: 4,193 Bytes
74cb9b4
1c6e1aa
74cb9b4
 
 
 
 
1c6e1aa
74cb9b4
 
 
 
 
 
 
 
 
 
 
 
1c6e1aa
74cb9b4
 
 
 
 
 
 
1c6e1aa
74cb9b4
 
 
 
1c6e1aa
74cb9b4
 
 
1c6e1aa
74cb9b4
 
 
 
 
 
1c6e1aa
74cb9b4
 
 
 
 
 
1c6e1aa
74cb9b4
 
 
 
 
 
 
 
 
 
 
1c6e1aa
74cb9b4
 
 
 
 
 
 
 
 
 
 
 
1c6e1aa
74cb9b4
 
 
 
 
 
 
 
 
 
 
 
 
1c6e1aa
74cb9b4
 
 
 
 
 
 
 
 
 
 
 
1c6e1aa
74cb9b4
 
 
 
1c6e1aa
 
 
74cb9b4
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
import shodan
import gradio as gr
import logging
from abc import ABC, abstractmethod
from typing import List, Optional
import json
import asyncio

# Logging setup
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(message)s",
    handlers=[logging.StreamHandler()]
)
logger = logging.getLogger(__name__)

# Custom exceptions
class ScannerException(Exception):
    """Base exception for scanner errors."""
    pass

# Instance class (assuming a basic structure since it wasn’t provided)
class Instance:
    """Represents an Ollama instance."""
    def __init__(self, ip: str, port: int, owner_id: Optional[str] = None):
        self.ip = ip
        self.port = port
        self.owner_id = owner_id

    @classmethod
    def from_shodan(cls, result: dict, owner_id: Optional[str] = None) -> 'Instance':
        """Create Instance from Shodan result."""
        return cls(ip=result['ip_str'], port=result['port'], owner_id=owner_id)

    def to_dict(self) -> dict:
        """Convert to dict for JSON output."""
        return {"ip": self.ip, "port": self.port, "owner_id": self.owner_id}

# Scanner interface
class IScanner(ABC):
    """Scanner interface for finding Ollama instances."""
    @abstractmethod
    async def scan_instances(self, limit: int = 100) -> List[Instance]:
        pass

# ShodanScanner implementation
class ShodanScanner(IScanner):
    """Scanner for finding public Ollama instances using Shodan."""
    def __init__(self, api_key: str, owner_id: Optional[str] = None):
        self.api_key = api_key
        self.owner_id = owner_id

    async def scan_instances(self, limit: int = 100) -> List[Instance]:
        """Scan for Ollama instances using Shodan."""
        try:
            api = shodan.Shodan(self.api_key)
            results = api.search('port:11434 "Ollama"', limit=limit)
            instances = [Instance.from_shodan(result, self.owner_id) for result in results['matches']]
            return instances
        except shodan.APIError as e:
            raise ScannerException(f"Shodan API error: {str(e)}")
        except Exception as e:
            raise ScannerException(f"Error during Shodan scan: {str(e)}")

# Gradio processing function
async def scan_ollama_instances(api_key: str, limit: int, owner_id: str = "") -> str:
    """Process Shodan scan and return results."""
    try:
        if not api_key:
            return "Yo, drop a Shodan API key, fam!"
        
        scanner = ShodanScanner(api_key, owner_id if owner_id else None)
        instances = await scanner.scan_instances(limit=limit)
        
        if not instances:
            return "No Ollama instances found, dawg!"

        output = ["Found Ollama Instances:"]
        for i, instance in enumerate(instances, 1):
            output.append(f"{i}. {json.dumps(instance.to_dict(), indent=2)}")
        
        logger.info(f"Scanned {len(instances)} Ollama instances")
        return "\n\n".join(output)
    
    except ScannerException as e:
        logger.error(f"Scan failed: {str(e)}")
        return f"Scan crashed: {str(e)}"
    except Exception as e:
        logger.error(f"Unexpected error: {str(e)}")
        return f"Shit hit the fan, fam: {str(e)}"

# Gradio wrapper to run async function
def gradio_scan(api_key: str, limit: int, owner_id: str) -> str:
    """Wrapper to run async scan in Gradio."""
    return asyncio.run(scan_ollama_instances(api_key, limit, owner_id))

# Gradio interface
demo = gr.Interface(
    fn=gradio_scan,
    inputs=[
        gr.Textbox(label="Shodan API Key", placeholder="Enter your Shodan API key here", type="password"),
        gr.Slider(label="Max Results", minimum=1, maximum=1000, value=100, step=1),
        gr.Textbox(label="Owner ID (Optional)", placeholder="e.g., your_user_id", value="")
    ],
    outputs=gr.Textbox(label="Ollama Instances Found"),
    title="Ollama Instance Scanner",
    description="Scan for public Ollama instances using Shodan, Bay Area style! Drop your API key and let’s roll.",
    allow_flagging="never"
)

if __name__ == "__main__":
    logger.info("Firin’ up the Ollama scanner on Gradio...")
    demo.launch(server_name="0.0.0.0", server_port=7860)