UBF -- The Ultimate Bitmessage Forum

User info

Welcome, Guest! Please login or register.


You are here » UBF -- The Ultimate Bitmessage Forum » privacy & anti-NSA » TOR IRC -- release thread - fully anon text chat - single py src file


TOR IRC -- release thread - fully anon text chat - single py src file

Posts 1 to 10 of 10

1


copy & paste kills indentation !

therefore the code will be maintained at:

https://gist.github.com/torifier/f1a7c1 … 87df2c5347

below code is outdated , just for a quick orientation!

Code:
#!/usr/bin/env python2.7 
# -*- coding: utf-8 -*-


###     TOR  IRC     ###         Fully anonymous text chat. Launch it using your python language interpreter in a bash shell / Konsole.

#   release home site                https://gist.github.com/torifier/f1a7c1ac7b6b003cd9e1c187df2c5347
#   version   1.97                      
#   find chat partners, see also:     https://beamstat.com/chan/general      
#   UBF                                      bitmessage.mybb.im                   
#   UBF release thread          :     http://bitmessage.mybb.im/viewtopic.php?id=21
#   original Project homepage   :     https://github.com/froozen/torirc    
#   new Project homepage        :     https://github.com/torifier
#   wiki                        :     https://github.com/torifier/torirc/wiki



#   The project simply consists of this very file - a single python script.
#   No additional file is needed from  github.com/froozen/torirc  to run torIRC . Very handy and kewl !
#   TorIRC is also published on Bitmessage, chan "general" as a torIRC.tar archive file to avoid indentation problems after text-copy-paste. 
#   Right-click, "save BM as file" named "torIRC.tar" and untar the .py scriptfile.
#   Else, save this whole text as a file named   " torIRC.py "                         ---  it is a python v2.7 executable script


#  With TOR running, run torIRC as        ./torIRC.py    --server MY-CHAT              ---  this will start a chat server, now you need clients to connect to it

#  The server will tell you how to connect , such as :


# [i] Adding hidden service...  hit CTRL-C to stop your chat server 
# [C] Hostname is     savjd7h4riras2aq.onion
# [i] Server Active
# [i] Connect with the command 

#     ./torirc.py --connect=savjd7h4riras2aq.onion  

#################### ./torirc.py --connect              will run the client mode, so you can actually chat

#################### ./torirc.py --server               will run the server mode, to which the clients must connect. CTRL-C will stop server.


# In the Client window, you'll see sth. like:

	#  ~$./torirc.py  -c x6nz4zuolgq5hhkr.onion
	#  Trying to connect to x6nz4zuolgq5hhkr.onion:11009
	#  clientConnection: Connected to x6nz4zuolgq5hhkr.onion


# Now you can start to text chat !    In the client window, type    /help    for a documentation help text.
# It takes ca. one minute, then you have a chat server going !   So kewl !
# No need to edit /etc/tor/torrc  - except sometimes. 
# Make sure the port numbers match up between this file (user var section) and torrc .

# Test TOR-IRC by opening 3 Konsole-windows, then run 1 server and also run 2 clients. Then you can chat with yourself in the 2 client windows.
# Expect 2-5 seconds of delay for every chat line.


# STEM is located at             https://stem.torproject.org

# Full auto mode with STEM library, 
# install STEM and all other prerequisites beforehand with your package manager. 
# Run run_tests.py -a   inside STEM lib dir from downloaded stem-master.zip to see if anything is reported missing , e.g. :

# socksiPy             https://pypi.python.org/pypi/SocksiPy

########## stem needs:
# mock
# pyflakes
# pep8
# pycrypto
# tox











######################## to reduce the torIRC project into this single one .py-file, the original README file is attached below:
######################## no additional files needed, tor-IRC is now an "all-in-one-file project"     :-)



#                        TorIRC


#Anonymous IRC-like multiuser chat using TOR hidden services, with emphasis in network-analysis protection.

#This is a simple client/server chat using TOR hidden services and the python Stem controller library, implemented in a single python file. 
#License is GNU-GPL
#Usage:

#torirc.py [options]

#Options:
  #-h, --help                          show this help message and exit
  #-c CONNECT, --connect=some-Server
                                      #Acts as client, connect to server
  #-s, --server                        Acts as server

#Example use:

#This is a console-only application. 
#You need a recent version of TOR configured and in your path. 
#Also the time of the computer needs to be correct or else TOR won't connect.

#In the Server:

#~$ sudo -u debian-tor ./torirc.py -s #EXAMPLESRV
#[i] Connecting to TOR via Stem
#[i] Tor relay is alive. 369347 bytes read, 317787 bytes written.
#[C] Tor Version: 0.2.3.22-rc (git-4a0c70a817797420)
#[C] Socks port is: 9050
#[i] Adding hidden service...
#[C] Hostname is x6nz4zuolgq5hhkr.onion
#[i] Server Active
#[i] Connect with the command "./torirc.py --connect=x6nz4zuolgq5hhkr.onion"

#In the Client:

#~$./torirc.py  -c x6nz4zuolgq5hhkr.onion
#Trying to connect to x6nz4zuolgq5hhkr.onion:11009
#clientConnection: Connected to x6nz4zuolgq5hhkr.onion

#You will be assigned a randomly generated nick. You need to set your nick with '/nick' and you are good to go. If you want multiple chatrooms, start multiple servers, each one will auto-generate their own hidden-service url.
#Objectives

#Anonymous/Encrypted chat resistant to:

    #Network analysis techniques
    #Exploits
    #Crypto attacks
    #Trust minimization

#To reach those objectives the design of torirc follows:

    #Simplicity: Small means less bugs and easier to audit
    #Interpreted language: Avoid most memory corruption bugs
    #Minimize library use: Again, less code susceptible to bugs
    #Entropy maximization: When possible, random delays and useless data is transmitted.

#Discussion of choices

    #Python: I selected python because it's what I know, and the interpreter is relatively small. Second choice would have been Java, but the JRE is too big and cumbersome. Also Python usually comes installed in most Linux distros. Also TOR has Stem, a very nice python-controller lib.

    #TOR: big ugly chunk of C code that I do not trust entirely, but at this time is the only software that provides the functionality that I need, that is, hidden services and onion routing. Also, the current version of torirc doesn't have his own cryptography routines and uses TOR for it, but this may change in the future.

#Alternatives

#Here are alternative software and why I do not like it:

    #IRC over tor: This is the best alternative, but only if you don't use any public server. Anyway this is vulnerable to exploits as IRC servers and clients tend to be huge pieces of C code. Also Network analysis is trivial with this protocol.

    #MSN/Gtalk/Pidgin: Horrible choices, huge codebases, hundreds of libraries riddled with bugs, vulnerable to exploits, central server sees all your (often plaintext) messages, etc. Some plugins like OTR fix some shortcomings, but network analysis is also trivial.

    #Silc: They wrote their own crypto, that's a big mistake. Also, it's written in C. I do believe they also don't protect against network analysis.

    #torchat: Nice alternative but only P2P, latest versions started to creep with unsafe functionality like emoticons, etc.

#Network analysis protections

#This still is experimental software so no strong network-analysis-proof must be assumed. At this moment:

    #Thanks to TOR, nobody, the server or the clients, known the IP address of nobody else.
    #However, the server knows when a client is connected.
    #The client periodically sends random data at random intervals.
    #Every message is padded to minimum_message_len (currently 256 bytes)
    #The server doesn't accurately report the number of clients in the chatroom, it only erases a nick approximately a day after it disconnects (this delay is also random)

#Network analysis is a hard problem and there are hundreds of side-channels that can be used to determine if a user is connected or not. This information can be the difference between life and death for some people, so it's a useful problem to tackle IMHO.
#Stem

#Latest tor-irc version uses the Stem python library to connect and control TOR, and now it makes uses of the system TOR daemon instead of spawning it's own TOR process. This is more clean but it requires you to install the Stem library and configure the TOR control port. If you do not want to do this, the torirc-nostem.py script doesn't uses Stem, but it's bigger and uglier.



# still TO DO:
# Authentication                    # to restrict access to the tor process
# Network-timing analysis graph








#############################################################################  python source part below









# fixed June 19th, 2016 by some BitMessage person

# Copyright (C) 2012-2013, Alfredo Ortega <alfred@groundworkstech.com>
# All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA,
# or google it.

# licence see:  https://github.com/alfred-gw/torirc/blob/master/COPYING



__author__="alfred"
__date__ ="$Jul 30, 2013$"

import curses
from   threading import Thread
from   optparse  import OptionParser
import time,os,subprocess
import socket,select,random,sys
import tempfile







## ---------- Start of user-configurable variables ----------------

minimum_message_len=256

# Network-related variables
tor_server='127.0.0.1'

# Used if can't load it from configuration
tor_server_control_port=9051
tor_server_socks_port=  9150 
########################################################################################################  adjust this if you want
hidden_service_interface='127.0.0.1'
hidden_service_port=11009

## Time "noise". Increase this value in dire situations
clientRandomWait=5   # Random wait before sending messages
clientRandomNoise=10 # Random wait before sending the "noise message" to the server
serverRandomWait=5   # Random wait before sending messages 

## Gui
buddywidth=20

## ---------- End of user-configurable variables -----------------







# lists for the gui
chantext=[]
roster=[]

## Tor stem glue class

class torStem():
	def connect(self,addr='127.0.0.1',cport=9051):

    print "[i] Connecting to TOR via Stem library"
    # Load Stem lib
    try:
    	from stem.control import Controller
    except:
    	print "[E] Cannot load stem module."
    	print "[E] Try installing python-stem with the package manager of your distro ('pacman' or whatever)"
    	exit(0)
    # Connect to TOR
    self.controller = Controller.from_port(address=addr,port=cport)
    self.controller.authenticate()  # provide the password here if you set one
	
    bytes_read = self.controller.get_info("traffic/read")
    bytes_written = self.controller.get_info("traffic/written")
	
    print "[i] Tor relay is alive. %s bytes read, %s bytes written." % (bytes_read, bytes_written)
    print "[C] Tor Version: %s" % str(self.controller.get_version())
    # Get socks port
    try:
    	self.SocksPort=self.controller.get_conf("SocksPort")
    	if self.SocksPort==None:
        self.SocksPort=9050
    	else:	self.SocksPort=int(self.SocksPort)
    	print "[C] Socks port is: %d" % self.SocksPort
    except: 
    	print "[E] Failed to get Socks port, trying 127.0.0.1:9050..."
    	self.SocksPort=9050
    	pass






    # Add hidden service  ----------------------------------------------- error fixed now in 2016
    print "[i] Adding hidden service.  Hit CTRL-C to stop server afterwards.  Please wait ca. one minute until hidden service is ready."
    
                self.hostname = self.controller.create_ephemeral_hidden_service({hidden_service_port: '%s:%d' % (hidden_service_interface, hidden_service_port)}, await_publication = True).service_id + '.onion'
                print "[C] Hostname is %s" % self.hostname



        def disconnect(self):
          # Remove hidden service
          print "Removing hidden service and shutting down torIRC." 
 
          self.controller.remove_ephemeral_hidden_service(self.hostname.replace('.onion', ''))
 
  
  
  
  
            
            
# stuff from 2013 not working any longer :    



    #newHiddenServiceDir=tempfile.mkdtemp()
    #self.origConfmap = self.controller.get_conf_map("HiddenServiceOptions")
    #self.controller.set_options([
      	#('HiddenServiceDir' ,self.origConfmap["HiddenServiceDir"]),
    	#('HiddenServicePort',self.origConfmap["HiddenServicePort"]),
      	#('HiddenServiceDir',newHiddenServiceDir),
    	#('HiddenServicePort',"%d %s:%d" % (hidden_service_port,hidden_service_interface,hidden_service_port))
    	#])
    #self.hostname=open("%s/hostname" % newHiddenServiceDir,"rb").read().strip()




	#def disconnect(self):
	  ## Remove hidden service
	  #print "Removing hidden service..."
	  #self.controller.set_options([
	  	#('HiddenServiceDir',self.origConfmap["HiddenServiceDir"]),
    #('HiddenServicePort',self.origConfmap["HiddenServicePort"])
    #])




## Log Mode (Server logs to stdout, client do not)
STDoutLog=False

# Add padding to a message up to minimum_message_len
def addpadding(message):
	if len(message)<minimum_message_len:
    message+=chr(0)
    for i in range(minimum_message_len-len(message)):
    	message+=chr(random.randint(ord('a'),ord('z')))
	return message
    

## Return sanitized version of input string
def sanitize(string):
	out=""
	for c in string:
    if (ord(c)==0): break # char(0) marks start of padding
    if (ord(c)>=0x20) and (ord(c)<0x80):
    	out+=c
	return out

## Log function
## Logs to STDOut or to the chantext channel list
def log(text):
	if (STDOutLog):
    print text
	else:
    maxlen=width-buddywidth-1
    while (True):
    	if (len(text[:maxlen])>0):
        chantext.append(text[:maxlen])
    	text=text[maxlen:]
    	if text=='':
        break
    redraw(stdscr)
    stdscr.refresh()


### Server class
# Contains the server socket listener/writer

class Server():
	# Server roster dictionary: nick->timestamp
	serverRoster={}

	## List of message queues to send to clients
	servermsgs=[]

	## channel name
	channelname=""

	## Eliminate all nicks more than a day old
	def serverRosterCleanThread(self):
    while True:
    	time.sleep(10)
    	current=time.time()
    	waittime = random.randint(60*60*10,60*60*36)         # 10 hours to 1.5 days
    	for b in self.serverRoster:
        if current-self.serverRoster[b]>waittime:    # Idle for more than the time limit
        	self.serverRoster.pop(b)             # eliminate nick
        	waittime = random.randint(60*60*10,60*60*36)
    	
	## Thread attending a single client
	def serverThread(self,conn,addr,msg,nick):
    log("(ServerThread): Received connection - a buddy connected !")
    conn.setblocking(0)
    randomwait=random.randint(1,serverRandomWait)
    while (True):
    	try:
        time.sleep(1)
        ready = select.select([conn], [], [], 1.0)
        if ready[0]:
        	data=sanitize(conn.recv(minimum_message_len))
        	if len(data)==0: continue
        	message="%s: %s" % (nick,data)
        	# Received PING, send PONG
        	if data.startswith("/PING"):
            message=""
            msg.append(data)
            continue
        	# Change nick. Note that we do not add to roster before this operation
        	if data.startswith("/nick "): 
            newnick=data[6:].strip()
            if newnick.startswith("--"):continue
            log("Nick change: %s->%s" % (nick,newnick))
            nick=newnick
            self.serverRoster[newnick]=time.time() # save/refresh timestamp
            message="Nick changed to %s" % newnick
            msg.append(message)
            continue
        	# Return roster
        	if data.startswith("/roster"):
            message = "--roster"
            message+=" %s" % self.channelname
            totalbuddies=len(self.servermsgs)
            for r in self.serverRoster:
            	message+=" %s" % r
            	totalbuddies-=1
            message+=" --anonymous:%d" % totalbuddies
            msg.append(message)
            continue
        	if data.startswith("/serverhelp"):
            msg.append("These are the commands which are supported:")
            msg.append("     /serverhelp         : Send this help text")
            msg.append("     /roster             : Send the list of connected buddies")
            msg.append("     /nick <my-new-name> : Changes your nickname")
            continue
        	# refresh timestamp
        	self.serverRoster[nick]=time.time() 
        	# Send 'message' to all queues
        	for m in self.servermsgs:
            m.append(message)
        # We need to send a message
        if len(msg)>0:
        	randomwait-=1 # Wait some random time to add noise
        	if randomwait==0:
            m = addpadding(msg.pop(0))
            conn.sendall(m)
            randomwait=random.randint(1,serverRandomWait)
        # Random wait before sending noise to the client
        if random.randint(0,clientRandomNoise)==0: 
        	ping="/PING "
        	for i in range(120):
            ping+="%02X" % random.randint(ord('a'),ord('z'))
        	msg.append(ping)
    	except:
        self.servermsgs.remove(msg)
        conn.close()
        print "exiting: msgs %d" % len(self.servermsgs)
        raise

	## Server main thread
	def serverMain(self,channel_name):
    global STDOutLog
    STDOutLog=True
    self.channelname=channel_name
    # Connects to TOR and create hidden service
    self.ts=torStem()
    try:
    	self.ts.connect(tor_server,tor_server_control_port)
    except Exception as e:
    	log("[E] %s" % e)
    	log("[E] Check if the control port is activated in /etc/tor/torrc")
    	log("[E] Try to run as the same user as tor, i.e.   sudo -u debian-tor ./torirc.py -s MY-CHAT  (maybe useful or not) ")
    	exit(0)
##                                                                                          advice is not useful in Arch Linux with user "tor" 
    # Start server socket
    s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 
    s.bind((hidden_service_interface,hidden_service_port))    
    log('[i] chat Server now Active')
    log('[i] Connect in order to chat typing the command "%s --connect=%s"' % (sys.argv[0],self.ts.hostname))
    s.listen(5)
    # Create server roster cleanup thread
    t = Thread(target=self.serverRosterCleanThread, args=())
    t.daemon = True
    t.start()
    while True:
    	try:
        conn,addr = s.accept()
        cmsg=[]
        nick="anon_%d" % random.randint(0,10000)
        cmsg.append("Welcome %s, this is %s" % (nick,self.channelname))
        self.servermsgs.append(cmsg)
        t = Thread(target=self.serverThread, args=(conn,addr,cmsg,nick))
        t.daemon = True
        t.start()
    	except KeyboardInterrupt:
        self.ts.disconnect()
        log("[i] (Main chat Server Thread): Exiting")
    	        exit(0)
    	except:
        pass

## Client commands
commands =[]

def chat_help():
	pass

# Client Help
def chat_help(args): 
	chantext.append("\ttor-IRC, %s %s" % (__author__,__date__))
	chantext.append("\tCommands available:")
	for c in commands:
    chantext.append("\t\t/%s: %s" % (c[0],c[2]))
	return ""
commands.append(("help",chat_help,"Local help"))


# Server help
def chat_server_help(args): 
	return "/serverhelp"
commands.append(("serverhelp",chat_server_help,"Request server commands help text"))

# Quit
def chat_quit(args): 
	exit(0)
commands.append(("quit",chat_quit,"Exit the python application 'TOR-IRC'"))

## --- end client commands

## Client GUI functions

count=0
cmdline=""
inspoint=0
pagepoint=0

def changeSize(stdscr):
	global width,height
	size = stdscr.getmaxyx()
	width=size[1]
	height=size[0]

def redraw(stdscr):
	global textpad
	global roster
	stdscr.clear()
	# draw Text
	line=height-3
	for i in reversed(range(len(chantext)-pagepoint)):
    try:
    	stdscr.addstr(line,0,chantext[i],0)
    	if line==0: break
    	else: line-=1
    except:
    	pass
	# draw roster
	for i in range(len(roster)):
    buddy=roster[i]
    stdscr.addstr(i,width-buddywidth+1,str(buddy),0)
	# draw lines
	stdscr.hline(height-2,0,curses.ACS_HLINE,width)
	stdscr.vline(0,width-buddywidth,curses.ACS_VLINE,height-2)
	# prompt
	prompt="~ "
	stdscr.addstr(height-1,0,"%s%s" % (prompt,cmdline),0)
	stdscr.move(height-1,len(prompt)+inspoint)

# Process command line
# Returns string to send to server
def processLine(command):
	if command.startswith("/"):
    comm=command[1:].split(' ')
    for t in commands:
    	if comm[0].startswith(t[0]):
        func=t[1]
        return func(comm)
	return command


# Client connection thread
def clientConnectionThread(stdscr,ServerOnionURL,msgs):
	global roster
	# Try to load Socksipy
	try:
    import socks
	except:
    print "[E] Cannot load socksiPy module."
    print "[E] Try installing python-socksiPy with package manager of your distro : pypi.python.org/pypi/SocksiPy  "
    exit(0)
	while(True):
    try: 
    	log("Trying to connect to %s:%d" % (ServerOnionURL,hidden_service_port))
    	## Connects to TOR via Socks
    	s=socks.socksocket(socket.AF_INET,socket.SOCK_STREAM)
    	s.setproxy(socks.PROXY_TYPE_SOCKS5,tor_server,tor_server_socks_port)
    	s.settimeout(100)
    	s.connect((ServerOnionURL,hidden_service_port))
    	s.setblocking(0)
    	log("clientConnection: Connected to %s" % ServerOnionURL)
    	log("clientConnection: Autorequesting roster...")
    	msgs.append("/roster")
    	randomwait=random.randint(1,clientRandomWait)
    except:
    	log("clientConnection: Cannot connect! retrying...")
    	time.sleep(1)
    	continue
    try:
    	while(True):
        time.sleep(1)
        ready = select.select([s], [], [], 1.0)
        # received data from server
        if ready[0]:
        	data=sanitize(s.recv(minimum_message_len))
        	# received pong (ignore)
        	if data.find("/PING ")>-1:
            continue 
        	# received roster list
        	if data.startswith("--roster"):
            roster=[]
            for i in data.split(' ')[1:]:
            	roster.append(i)
        	# Write received data to channel
        	log(data)
        # We need to send a message
        if len(msgs)>0:  
        	randomwait-=1 # Wait some random time to add noise
        	if randomwait==0:
            m = addpadding(msgs.pop(0))
            s.sendall(m)
            randomwait=random.randint(1,clientRandomWait)
        # send noise in form of PINGs
        if random.randint(0,clientRandomNoise)==0:
        	ping="/PING "
        	for i in range(120):
            ping+="%02X" % random.randint(0,255)
        	#log("Sending %s" % ping)
        	msgs.append(ping)
    except:
    	s.close()
    	pass


## Client main procedure
def clientMain(stdscr,ServerOnionURL):
	global cmdline
	global inspoint
	global pagepoint
	global width,height
	changeSize(stdscr)
	redraw(stdscr)
	
	## Message queue to send to server
	msgs=[]
	t = Thread(target=clientConnectionThread, args=(stdscr,ServerOnionURL,msgs))
	t.daemon = True
	t.start()

	# Main Loop
	while True:
    input=stdscr.getch()

    # event processing
    if (input == curses.KEY_RESIZE):
    	changeSize(stdscr)
    # Basic line editor
    if (input == curses.KEY_LEFT) and (inspoint>0):
        inspoint-=1
    if (input == curses.KEY_RIGHT) and (inspoint<len(cmdline)):
        inspoint+=1
    if (input == curses.KEY_BACKSPACE) and (inspoint>0):
    	cmdline=cmdline[:inspoint-1]+cmdline[inspoint:]
    	inspoint-=1
    if (input == curses.KEY_DC) and (inspoint<len(cmdline)):
    	cmdline=cmdline[:inspoint]+cmdline[inspoint+1:]
    if (input == curses.KEY_HOME):
    	inspoint=0
    if (input == curses.KEY_END):
    	inspoint=len(cmdline)
    #PgUp/PgDown
    if (input == curses.KEY_PPAGE):
    	pagepoint+=height-2
    	if len(chantext)-pagepoint<0:
        pagepoint=len(chantext)
    if (input == curses.KEY_NPAGE):
    	pagepoint-=height-2
    	if pagepoint<0: pagepoint=0
    #History: TODO
    """
    if (input == curses.KEY_UP):
    if (input == curses.KEY_DOWN):
    """
    if (input == 10):
    	tosend=processLine(cmdline)
    	if len(tosend)>0:
        msgs.append(tosend)
    	cmdline=""
    	inspoint=0

    # Ascii key
    if input>31 and input<128:
    	if len(cmdline)<(width-5):
        cmdline=cmdline[:inspoint]+chr(input)+cmdline[inspoint:]
        inspoint+=1
    redraw(stdscr)

# Client
# Init/deinit curses 
def Client(ServerOnionURL):
  global stdscr
  global STDOutLog 
  STDOutLog=False

  try:
      # Initialize curses
      stdscr=curses.initscr()
      curses.noecho()
      curses.cbreak()
      stdscr.keypad(1)
      # Enter the main loop
      clientMain(stdscr,ServerOnionURL)
      # Set everything back to normal
      stdscr.keypad(0)
      curses.echo()
      curses.nocbreak()
      # Terminate curses
      curses.endwin() 
      exit(0)
  except:
      # In event of error, restore terminal to sane state.
      stdscr.keypad(0)
      curses.echo()
      curses.nocbreak()
      curses.endwin()
	

# Main proc:
# Parse options, invoke Server or Client
if __name__=='__main__':
  parser = OptionParser()
  parser.add_option("-c", "--connect", action="store", type="string", dest="connect",      help="Acts as client, connect to server")
  parser.add_option("-s", "--server" , action="store", type="string", dest="channel_name", help="Acts as server")
  	# no arguments->bail
  if len(sys.argv)==1:
  	parser.print_help()
	exit(0)
  (options, args) = parser.parse_args()
  if options.channel_name:
  	s=Server()
	s.serverMain(options.channel_name)
  else:
  	if len(options.connect)>0:
	   	Client(options.connect)
	else: parser.print_help()

2

much safer than any chat relying on some centralized online host run by NSA ...

:cool:

3

fuck it !

just found out that copy & paste fucks up indentation and stuff on the .py src

look for .tar post on BM or a github fork

can these web fools not get ONE simple thing right ?

what a fucking disgrace.

4

Since Python is indentation sensitive might I suggest that the source file be posted as an attachment?  Copy/pasting the text from here screws up the indentation.

5

https://gist.github.com/torifier/f1a7c1 … 87df2c5347

gist is much better than UBF for code release


future releases will be on gist - hub by torifier or forkers

6

that is a neat chat system

I offer stuff on BM chan general

go to beamstat.com

then we chat about prices...

7

bm6hsivrmdnxmw2f.onion/chan/torirc-rendezvous

post there before you want to meet in torIRC !

find chat partners !

8

torirc-rendezvous :

CLEARNET link :

bm6hsivrmdnxmw2f.onion.to/chan/torirc-rendezvous

9

update version 2.5

10

Code:

#!/usr/bin/env python2.7 
# -*- coding: utf-8 -*-

__author__=" torIRC v2.53 " # by torifier new=timlocal, tim2.4=old 
__doc__=" torIRC text chat program "
__date__=" Dec 18th, 2017 "      
'''

changelog:  * From v2.4 on, old onions will be reused from file ./my-tor-IRC-service-key      
              your published onions will be useful longer for clients.
              There is no need anymore to add 2 lines to the torrc file. Now easier to use!

This is a Python v.2 script text file program, which you can run (after download & save) by typing in a (e.g. Linux) xterm shell console:      ./torIRC.py --help
That will launch/start the real tornet TEXT CHAT program and output some info to help you to use it.
There is no need for compiling (or any such complicated thing) of a python 2 script like the one you are reading right now.


This is the TBB version of torIRC set to port 9150 rather than 9050. It is probably easier to use TorBrowserBundle (TBB) on 9150 rather than the standalone tor service on socksport 9050.  
Use port 11009 (already set up by default) - else try 1801 - to connect to standard torIRC servers.

 minified version for easy audit      release home site with documentation:    https://gist.github.com/torifier/f1a7c1ac7b6b003cd9e1c187df2c5347
 explanatory screenshot               http://screenshots.debian.net/screenshots/000/015/934/large.png
 prepare meet-up via                  http://bm6hsivrmdnxmw2f.onion.to/chan/torirc-rendezvous           
 or without webproxy in TBB           http://bm6hsivrmdnxmw2f.onion/chan/torirc-rendezvous                 <---- 2017 update releases posted in there
 see also:                            http://bitmessage.mybb.im/viewtopic.php?id=21#p109

 client mode is easy to achieve if someone has announced a working server and told you his onion address to connect to via    ./torIRC.py  --connect=XXX.onion 
 
 To run torIRC , on 2017 Linux distros typically only 3 requirements need to be installed besides python2 itself: TBB, stem, socksipy ( 1 file named socks.py ) download here:
 
https://kent.dl.sourceforge.net/project/socksipy/socksipy/SocksiPy%201.00/SocksiPy.zip
https://www.torproject.org/projects/torbrowser.html.en
https://pypi.org/project/stem/

installing stem can be tricky. Maybe try various combinations of:    sudo -H pip install --upgrade stem    (ver 1.6+)
sudo , sudo -H , pip install stem , easy_install --user stem , or without --user , pip3 ... , sudo -H python2 -m pip install stem 
sudo -H python2 ./setup.py install (in unzipped stem sources)   or something



################################################               outdated!   in v2.4+  you don't need this step any longer            ###########
Server mode requires 2 lines torrc setup:
in "torrc" at   ....TBB/tor-browser_en-US/Browser/TorBrowser/Data/Tor/   under your TorBrowserBundle (= TBB) subdir add these lines to enable server mode 
 
HiddenServiceDir  /.........................TBB/tor-browser_en-US/Browser/TorBrowser/Data/Tor/YYY
HiddenServicePort 11009 127.0.0.1:1802
#                 11009                     here must match in the file of clients and the torIRC.py file which the server uses, all must use 11009 (else e.g. 1801) to meet each other.
edit the text if you want modifications, save and run it. Port 11009 is generally preferred, while 1801 is default "fall-back". Any free port will fundamentally work.
################################################   end of  :   outdated!   in v2.4+  you don't need this step any longer            ###########






              soxp ctrlp      chatp
tor version:  9050 9051  and  11009      --- adapt numbers if stuff is not working 
TBB version:  9150 9151  and  11009      --- 11009 might be locally occupied - e.g. if you run 2 servers concurrently - then resort to 1801 or whatever


TODO: randint --> better crypto algo 1-2 seconds.  , leaks?  , nyx?



'''
prompt="> "                                     # put   ~   or any other prompt characters you like in this line to see it in the entry line at the screen bottom

hidden_service_port     =     11009             # tell your clients if your server changed away from :11009 standard port ; 1801 was old standard for torIRC TBB-version, now deprecated, use as a fall-back port, e.g. because of conflict with "torchat" or in case of:    socket.error: [Errno 98] Address already in use
hidden_service_interface=     '127.0.0.1'

tor_server              =     '127.0.0.1'       # i.e. localhost
tor_server_control_port =     9151              # 9051 tor , TBB =  9151 
tor_server_socks_port   =     9150              # TBB offers 9150 instead of tor = 9050

clientRandomWait        =     2                 # seconds
clientRandomNoise       =     10
serverRandomWait        =     1 # 2   1 = no random
buddywidth              =     20
minimum_message_len     =     256

chantext   =[]
roster     =[]   # client-side only
commands   =[]
STDoutLog  =False

import curses,time,os,subprocess,socket,select,random,sys,tempfile
from   threading import Thread
from   optparse  import OptionParser


class torStem():
 def connect(self,addr='127.0.0.1',cport=tor_server_control_port):  # tor 9051   ; 9151 TBB
  print("[i] Connecting to TOR via Stem library") 
  try:
   from stem.control import Controller
  except:
   print "[E] Cannot load stem module."
   print "[E] Try installing python-stem with the package manager of your distro;  apt, 'pacman' ... ; can be tricky"
   exit(0)
  self.controller=Controller.from_port(address=addr,port=cport)
  self.controller.authenticate() 
  bytes_read=self.controller.get_info("traffic/read")
  bytes_written=self.controller.get_info("traffic/written")
  print "[i] Tor relay is alive. %s bytes read, %s bytes written."%(bytes_read,bytes_written)
  print "[C] Tor Version: %s"%str(self.controller.get_version())
  try:
   self.SocksPort=self.controller.get_conf("SocksPort")
   print "[C] Socks port 1 is: %d"%self.SocksPort
   if self.SocksPort==None:
    self.SocksPort=tor_server_socks_port  #9150 #9050
   else: self.SocksPort=int(self.SocksPort)
   print "[C] Socks port 2 is: %d"%self.SocksPort
  except:
   print "[E] Failed to get Socks port - do not worry, it is normal - trying 127.0.0.1:" , str(tor_server_socks_port)  # 9050  if tor, not TBB
   self.SocksPort=tor_server_socks_port                                                            #9150   #9050
   pass


  print "[i] Adding a 'Hidden Service'.  Hit CTRL-C to stop server afterwards.  Please wait ca. one minute until hidden service is ready."
  
  key_path = os.path.expanduser('./my-tor-IRC-service-key')
  if not os.path.exists(key_path):
   print("[i] no existing onion found, the file   my-tor-IRC-service-key  will be produced only now.")    
   self.Xservice = self.controller.create_ephemeral_hidden_service({hidden_service_port:'%s:%d'%(hidden_service_interface,hidden_service_port)}, await_publication = True)       # 80: 5000 flask # hidden_service_interface,hidden_service_port
   print("[i] Started a newly created  hidden service with the address of %s.onion" % self.Xservice.service_id)

   with open(key_path, 'w') as key_file:
    key_file.write('%s:%s' % (self.Xservice.private_key_type, self.Xservice.private_key))
  else:
   with open(key_path) as key_file:
    key_type, key_content = key_file.read().split(':', 1)
   print("[i] Resuming already existing onion.")  
   self.Xservice = self.controller.create_ephemeral_hidden_service({hidden_service_port:'%s:%d'%(hidden_service_interface,hidden_service_port)}, key_type = key_type, key_content = key_content, await_publication = True)
   print("[i] Resumed existing old   %s.onion" % self.Xservice.service_id)

  self.hostname=self.Xservice.service_id+'.onion'
  print "[C] re-useable Hostname is %s"%self.hostname

 def disconnect(self):
  print "Removing hidden service and shutting down torIRC." 
  self.controller.remove_ephemeral_hidden_service(self.hostname.replace('.onion',''))


def addpadding(message):
 if len(message)<minimum_message_len:
  message+=chr(0)
  for i in range(minimum_message_len-len(message)):
   message+=chr(random.randint(ord('a'),ord('z')))
 return message


def sanitize(string):
 out=""
 for c in string:
  if(ord(c)==0):break 
  if(ord(c)>=0x20)and(ord(c)<0x80):
   out+=c
 return out


def log(text):
 if(STDOutLog):
  print text
 else:
  maxlen=width-buddywidth-1
  while(True):
   if(len(text[:maxlen])>0):
    chantext.append(text[:maxlen])
   text=text[maxlen:]
   if text=='':
    break
  redraw(stdscr)
  stdscr.refresh()


class Server():
 serverRoster ={}
 servermsgs   =[]
 channelname  =""


 
 def serverRosterCleanThread(self):
  while True:
   time.sleep(127)                                              #   10 -->  113 sec       sync takes time
   current=time.time()
   waittime=float( random.randint(01*60*01,01*60*02) )          #  (01*60*01,01*60*02)    1 - 2 min.
   try:
    for b in self.serverRoster:                                 #  RuntimeError: dictionary changed size during iteration 
     if current - self.serverRoster[b] > waittime: 
      self.serverRoster.pop(b) 
      waittime = float( random.randint(01*60*10,01*60*15) )     #  (01*60*10,01*60*15)    10 - 15 m.
   except: 
    pass
    #log(str(" user is roster ghost:  " + str(b)))              # pop              dict. size exc. at  reappear after /nick renew
    #self.serverRoster.pop(b)                                   # KeyError: '2'    ghost users disappear after use of /roster
     




 def serverThread(self,conn,addr,msg,nick):
  log("(ServerThread): Received connection - a buddy connected !")
  conn.setblocking(0)
  randomwait=random.randint(1,serverRandomWait)
  while(True):
   try:
    time.sleep(1)
    ready=select.select([conn],[],[],1.0)
    if ready[0]:
     data=sanitize(conn.recv(minimum_message_len))
     if len(data)==0:continue
     message="%s: %s"%(nick,data)
     if data.startswith("/PING"):
      message=""
      msg.append(data)
      continue

     
     if data.startswith("/nick "):
      newnick=data[6:].strip()
      if newnick.startswith("--"):continue
      log("Nick change: %s->%s"%(nick,newnick))
      nick=newnick
      self.serverRoster[newnick]=time.time()
      message="Nick changed to %s"%newnick
      msg.append(message)
      continue
     
     
     if data.startswith("/roster"):
      message="--roster"
      message+=" %s"%self.channelname

      totalbuddies=len(self.servermsgs)
      hmany=totalbuddies # too hi after /nick
      for r in self.serverRoster:
       message+=" %s"%r
       totalbuddies -= 1   ## -=1   why?
      message+=" --total:%d"%totalbuddies     #  --anonymous:   count too low often
      msg.append(message) 
      continue
  
     if data.startswith("/serverhelp"):
      msg.append("Possible commands to enter in the entry line at the bottom:")
      msg.append("     /serverhelp         : Send this very help text")
      msg.append("     /roster             : Send the list of connected chat buddies")
      msg.append("     /nick <my-new-name> : Changes your nickname")
      continue
  
     self.serverRoster[nick]=time.time()
     for m in self.servermsgs:
      m.append(message)
    if len(msg)>0:
     randomwait-=1 
     if randomwait==0:
      m=addpadding(msg.pop(0))
      conn.sendall(m)
      randomwait=random.randint(1,serverRandomWait)
    if random.randint(0,clientRandomNoise)==0:
     ping="/PING "
     for i in range(120):
      ping+="%02X"%random.randint(ord('a'),ord('z'))
     msg.append(ping)
   except:
    self.servermsgs.remove(msg)
    conn.close()
    print "exiting: msgs %d"%len(self.servermsgs)
    raise





 def serverMain(self,aservername):
  global STDOutLog
  STDOutLog=True
  self.channelname=aservername
  self.ts=torStem()
  try:
   self.ts.connect(tor_server,tor_server_control_port)
  except Exception as e:
   log("[E] %s"%e)
   log("[E] Check if the control port is activated in /etc/tor/torrc    or maybe tor does not run currently    ")
   log("[E] Try to run as the same user as tor, i.e.   sudo -u debian-tor ./torirc.py -s MY-CHAT  (maybe useful or not) ")
   exit(0)
  s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
  s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
  s.bind((hidden_service_interface,hidden_service_port)) 
  log('[i] chat Server now Active')
  log('[i] In order to chat,  type in the command:     %s --connect=%s ' %(sys.argv[0],self.ts.hostname))
  s.listen(5)

  t=Thread(target=self.serverRosterCleanThread,args=())
  t.daemon=True
  t.start()
  
  while True:
   try:
    conn,addr=s.accept()
    cmsg=[]
    nick="anon_%d"%random.randint(0,10000)
    cmsg.append("Welcome %s, this is %s"%(nick,self.channelname))
    self.servermsgs.append(cmsg)
    t=Thread(target=self.serverThread,args=(conn,addr,cmsg,nick))
    t.daemon=True
    t.start()
   except KeyboardInterrupt:
    self.ts.disconnect()
    log("[i] (Main chat Server Thread): Exiting")
    exit(0)
   except:
    pass



def chat_help(args):
 chantext.append("\ttorIRC, %s %s"%(__author__,__date__))
 chantext.append("\tpossible client commands: ")
 for c in commands:
  chantext.append("\t\t/%s: %s"%(c[0],c[2]))
 return ""

commands.append(("help",chat_help,"Local help"))


def chat_server_help(args):
 return "/serverhelp"

commands.append(("serverhelp",chat_server_help,"Request server commands help text"))


def chat_quit(args):
 exit(33) #0

commands.append(("quit",chat_quit,"Exit the python application 'torIRC'"))
count=0
cmdline=""
inspoint=0
pagepoint=0


def changeSize(stdscr):
 global width,height
 size=stdscr.getmaxyx()
 width=size[1]
 height=size[0]


def redraw(stdscr):
 global textpad
 global roster
 stdscr.clear()
 line=height-3
 for i in reversed(range(len(chantext)-pagepoint)):
  try:
   stdscr.addstr(line,0,chantext[i],0)
   if line==0:break
   else:line-=1
  except:
   pass
 for i in range(len(roster)):
  buddy=roster[i]
  stdscr.addstr(i,width-buddywidth+1,str(buddy),0)
 stdscr.hline(height-2,0,curses.ACS_HLINE,width)
 stdscr.vline(0,width-buddywidth,curses.ACS_VLINE,height-2)
 stdscr.addstr(height-1,0,"%s%s"%(prompt,cmdline),0)
 stdscr.move(height-1,len(prompt)+inspoint)


def processLine(command):
 if command.startswith("/"):
  comm=command[1:].split(' ')
  for t in commands:
   if comm[0].startswith(t[0]):
    func=t[1]
    return func(comm)
 return command


def clientConnectionThread(stdscr,ServerOnionURL,msgs):
 global roster
 try:
  import socks  # just the single file socks.py from socksipy, all that is needed for client mode
 except:
  print "[E] Cannot load socksiPy module."
  print "[E] Try installing python-socksiPy with package manager of your distro : pypi.python.org/pypi/SocksiPy - just place single socks.py in the same subdir with torIRC "
  exit(0)
 chat_help("333")
 while(True):
  try:
   log("Trying to connect server at %s:%d"%(ServerOnionURL,hidden_service_port))
   s=socks.socksocket(socket.AF_INET,socket.SOCK_STREAM)
   s.setproxy(socks.PROXY_TYPE_SOCKS5,tor_server,tor_server_socks_port)
   s.settimeout(100)
   s.connect((ServerOnionURL,hidden_service_port))
   s.setblocking(0)
   log("clientConnection: Connected to %s"%ServerOnionURL)
   #log("clientConnection: Autorequesting roster...")
   msgs.append("/serverhelp")
   randomwait=random.randint(1,clientRandomWait)
   msgs.append("/roster")
  except:
   log("clientConnection: Cannot connect! retrying...")
   time.sleep(1)
   continue
  try:
   while(True):
    time.sleep(1)
    ready=select.select([s],[],[],1.0)
    if ready[0]:
     data=sanitize(s.recv(minimum_message_len))
     if data.find("/PING ")>-1:
      continue 
     if data.startswith("--roster"):
      roster=[]
      for i in data.split(' ')[1:]:
       roster.append(i)
     log(data)
    if len(msgs)>0: 
     randomwait-=1 
     if randomwait==0:
      m=addpadding(msgs.pop(0))
      s.sendall(m)
      randomwait=random.randint(1,clientRandomWait)   # improve on randint  -->  securerandom
    if random.randint(0,clientRandomNoise)==0:
     ping="/PING "
     for i in range(120):
      ping+="%02X"%random.randint(0,255)
     msgs.append(ping)
  except:
   s.close()
   pass


def clientMain(stdscr,ServerOnionURL):
 global cmdline
 global inspoint
 global pagepoint
 global width,height
 changeSize(stdscr)
 redraw(stdscr)
 msgs=[]
 t=Thread(target=clientConnectionThread,args=(stdscr,ServerOnionURL,msgs))
 t.daemon=True
 t.start()
 while True:
  input=stdscr.getch()
  if(input==curses.KEY_RESIZE):
   changeSize(stdscr)
  if(input==curses.KEY_LEFT)and(inspoint>0):
   inspoint-=1
  if(input==curses.KEY_RIGHT)and(inspoint<len(cmdline)):
   inspoint+=1
  if(input==curses.KEY_BACKSPACE)and(inspoint>0):
   cmdline=cmdline[:inspoint-1]+cmdline[inspoint:]
   inspoint-=1
  if(input==curses.KEY_DC)and(inspoint<len(cmdline)):
   cmdline=cmdline[:inspoint]+cmdline[inspoint+1:]
  if(input==curses.KEY_HOME):
   inspoint=0
  if(input==curses.KEY_END):
   inspoint=len(cmdline)
  if(input==curses.KEY_PPAGE):
   pagepoint+=height-2
   if len(chantext)-pagepoint<0:
    pagepoint=len(chantext)
  if(input==curses.KEY_NPAGE):
   pagepoint-=height-2
   if pagepoint<0:pagepoint=0
  #              if (input == curses.KEY_UP):
  #              if (input == curses.KEY_DOWN):
  if(input==10):
   tosend=processLine(cmdline)
   if len(tosend)>0:
    msgs.append(tosend)
   cmdline=""
   inspoint=0
  if input>31 and input<128:
   if len(cmdline)<(width-5):
    cmdline=cmdline[:inspoint]+chr(input)+cmdline[inspoint:]
    inspoint+=1
  redraw(stdscr)


def Client(ServerOnionURL):
 global stdscr
 global STDOutLog 
 STDOutLog=False
 try:
  stdscr=curses.initscr()
  curses.noecho()
  curses.cbreak()
  stdscr.keypad(1)
  clientMain(stdscr,ServerOnionURL)
  stdscr.keypad(0)
  curses.echo()
  curses.nocbreak()
  curses.endwin()
  exit(0)
 except:
  stdscr.keypad(0)
  curses.echo()
  curses.nocbreak()
  curses.endwin()


if __name__=='__main__':
 parser=OptionParser()
 parser.add_option ("-?"               ,action='store_true'         ,dest='bool2'                  , default=False            ,help="Show this help message about this 'torIRC' text chat client/server python2 programme '" + __author__  + "' and exit.")
 parser.add_option ("-s","--server"    ,action="store",type="string",dest="a_server_name_you_chose", default="ChatRoom11009"  ,help="Act as a server to some chat clients and produce a re-useable address.onion to be told to the clients. The name will show on top of the user roster. If you want to run a second server at the same time, you must make a copy of this .py file, change the 11009 port number to an unoccupied one, say 1801, and run that .py file. Port 11009 cannot be used by two or more chatservers at the same time, but can be used by many chat-clients.")
 parser.add_option ("-c","--connect"   ,action="store",type="string",dest="a_server_address_onion" , default=""               ,help="Act as a chat client and connect to a chat server. You need to be told the actual server address.onion , so you can connect to it. It is also possible to produce an onion (or several) for yourself and run a test chat to yourself in a separate console window (or several) to see if all works properly. Without a known server, the clients cannot chat.")
 parser.add_option ("-a","--autostart" ,action='store_true'         ,dest='bool1'                  , default=False            ,help="-a  or just an   a   or such letters will autostart a chat server re-using your old onion if its keyfile  ./my-tor-IRC-service-key  is present in current dir.")
 # autoconnect to std onion , provide key
 # autostart      std srv yourself , demo c/s with provided key, hint likely collision err msg
 
 if len(sys.argv)==1:
  parser.print_help()
  exit(88)  # maybe args got lost in call
 (options,args)=parser.parse_args()
 
 if options.bool2: 
  parser.print_help()
  exit(0) 
  
 if len( options.a_server_address_onion)>0: Client(options.a_server_address_onion)
 else: 
  parser.print_help()
  print "\n\n"
  s=Server()
  s.serverMain(options.a_server_name_you_chose)  


You are here » UBF -- The Ultimate Bitmessage Forum » privacy & anti-NSA » TOR IRC -- release thread - fully anon text chat - single py src file