Skip to content

Commit f1b6487

Browse files
committed
Initial Commit
0 parents  commit f1b6487

File tree

5 files changed

+2701
-0
lines changed

5 files changed

+2701
-0
lines changed

440SSHPaper.odt

93 KB
Binary file not shown.

README

+107
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
====440 Final Project====
2+
=========================
3+
Authors:
4+
Sam Sussman
5+
Charles Belanger
6+
Dustin Ernst
7+
Project:
8+
SSH Client and Server using Python and Paramiko
9+
10+
SSH Client README
11+
=============
12+
13+
sshclient.py
14+
15+
16+
What is this?
17+
sshclient.py is a ssh client implemented in python using paramiko.
18+
It also has support for sftp for transferring files, it shows the
19+
current working directory.
20+
21+
Packages Needed to have installed
22+
paramiko
23+
argparse
24+
Packages used
25+
paramiko, sys, os, time, termios, argparse, getpass,
26+
threading (Thread), Queue (Queue,Empty)
27+
28+
29+
How to run
30+
sshclient.py [-o -u -p -t --sftp]
31+
-o host name
32+
-u username
33+
-p password
34+
-t port
35+
=====================================================================
36+
SFTP commands: Issue ctrl a to start a sftp session.
37+
get put help exit ls cd cwd
38+
lwd lls lcd bye quit rm rmdir
39+
lrm rmdir ? rename symlink mkdir
40+
=====================================================================
41+
usage:
42+
use how you typically use ssh
43+
Bugs:
44+
Can not do ctrl a when sftp mode is active.
45+
certain exit conditions do not restore the terminal. ie can't see what you are
46+
typing if the script ends. Hard to debug because the conditions happened very
47+
rarely
48+
49+
50+
The functions:
51+
sftpcmds - holds all sftp commands
52+
53+
class sshClient(): -our ssh client
54+
def __init__(self,host,user,password,port):
55+
def sftpHandle(self,sftp): sftp handler
56+
def callSftpMethod(self,client,method,opts): sftp caller
57+
def makeInputRaw(self): raw imput
58+
def restoreInput(self): Restores out input
59+
when script ends
60+
def ioThreadFunc(self, fd, q): Thread for handling
61+
io
62+
def read_until(self, connect):
63+
def __del__(self): Deconstructor
64+
def notNull(arg,default=None): Arguments passing
65+
66+
67+
68+
69+
70+
71+
SSH Server README
72+
==============
73+
74+
sshServer.py
75+
76+
77+
Packages needed:
78+
paramiko - This will need to be installed (use pip install paramiko) and
79+
modified. Copy the provided transport.py over the one in the paramiko
80+
directory in your site-packages directory.
81+
82+
83+
The system will also need the libc and libpam shared libraries.
84+
85+
Make sure you also have the included pamAuth.py.
86+
87+
You'll need a private rsa key.
88+
Right now it uses the one that comes with OpenSSH and is used by a normal
89+
SSH server
90+
This will prevent you from having to mess around with your keys for
91+
your machine if you use the normal client.
92+
93+
94+
Execution:
95+
The server will need to be run with root permissions in order to successfully
96+
log in the user. It takes no arguments.
97+
98+
99+
Set the values at the top of the script if you need to change your host,
100+
port, or private rsa key location.
101+
102+
103+
This should be enough to get it running.
104+
Kill it with "kill"
105+
106+
Other documentation is elsewhere.
107+
Check out the included report and the comments in the source code.

sshClient.py

+240
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
#!/usr/bin/env python
2+
# Foundations of Python Network Programming - Chapter 16 - ssh_simple.py
3+
# Using SSH like Telnet: connecting and running two commands
4+
import fcntl, struct
5+
import paramiko
6+
import sys
7+
import os
8+
import time
9+
import termios
10+
import argparse, getpass, signal
11+
from threading import Thread
12+
from Queue import Queue,Empty
13+
14+
'''
15+
definition of a cmd with help for sftp
16+
'''
17+
class cmd:
18+
def __init__(self,help,call):
19+
self.help = help
20+
self.call = call
21+
22+
'''
23+
cmds for sftp
24+
'''
25+
sftpcmds = {
26+
"get": cmd("get remote_source local_dest",lambda client, opts: client.get(opts[0],opts[1])),
27+
"put": cmd("put remote_source, local_dest" ,lambda client, opts: client.put(opts[0],opts[1])),
28+
"help": cmd("help [cmd]",lambda client, opts: write((sftpcmds[opts[0]].help if len(opts) > 0 else str([x for x in sftpcmds.keys()])),True)),
29+
"exit": cmd("exit",lambda client, opts: write("exit")),
30+
"ls": cmd("ls [path]", lambda client, opts: write(str(client.listdir(opts[0] if len(opts) > 0 else '.')),True)),
31+
"cd": cmd("cd path", lambda client, opts: client.chdir(opts[0])),
32+
"cwd" : cmd("cwd -> remote current working directory", lambda client, opts: write(str(client.getcwd()),True)),
33+
"lcwd" : cmd("lcwd -> local current working directory",lambda client, opts: write(os.getcwd(),True)),
34+
"lls" : cmd("lls [path] -> local directory list", lambda client, opts: write(str(os.listdir(opts[0] if len(opts) > 0 else '.')),True)),
35+
"lcd" : cmd("lcd path -> local change directory", lambda client, opts: os.chdir(opts[0] if len(opts) > 0 else '.')),
36+
"bye" : cmd("bye", lambda client,opts: write('quit')),
37+
"quit" : cmd("quit -> exits application, does not return to ssh channel", lambda client,opts: sftpcmds["exit"].call(client,opts)),
38+
"rm" : cmd("rm path", lambda client, opts: client.remove(opts[0])),
39+
"rmdir" : cmd("rmdir path", lambda client, opts: client.rmdir(opts[0])),
40+
"lrm" : cmd("rm path", lambda client, opts: os.remove(opts[0])),
41+
"lrmdir" : cmd("rmdir path", lambda client, opts: os.rmdir(opts[0])),
42+
"?": cmd("?", lambda client,opts: sftpcmds["help"].call(client,opts)),
43+
"rename" : cmd("remname oldpath newpath", lambda client,opts: client.rename(opts[0],opts[1])),
44+
"symlink" : cmd("symlink oldpath newpath", lambda client,opts: client.symlink(opts[0],opts[1])),
45+
"mkdir" : cmd("mkdir path [mode]", lambda client, opts: client.mkdir(opts[0], int(opts[1]) if len(opts) > 1 else 511))
46+
}
47+
48+
49+
'''
50+
Runs and handles an ssh connection
51+
'''
52+
class sshClient(paramiko.SSHClient):
53+
'''
54+
Is the program loop, creates a connection then runs until it dies.
55+
'''
56+
def __init__(self,host,user,password,port,sftp,sftp_off):
57+
super(sshClient,self).__init__()
58+
self.running = True #run the character grabbing thread when true
59+
try:
60+
self._defaultattrs = self.makeInputRaw() #no echo, buffer, or signals from terminal
61+
self.set_missing_host_key_policy(paramiko.AutoAddPolicy())
62+
self.connect(host, username=user, password=password,port=port) #try to connect
63+
channel =self.invoke_shell() #get a shell
64+
def handle(*args): #handle terminal resize
65+
(height,width) = self.getWindowSize()
66+
channel.resize_pty(width,height)
67+
signal.signal(signal.SIGWINCH, handle)
68+
handle(None) #set the window size currently
69+
70+
self.inq = Queue() #character reading queue
71+
tI = Thread(target=self.ioThreadFunc, args=(sys.stdin, self.inq)) #start a thread that reads characters one by one off the stdin file
72+
tI.daemon = True
73+
tI.start()
74+
75+
while not channel.exit_status_ready():
76+
#time.sleep(.001) #don't loop too fast!
77+
try:
78+
if not sftp: #don't read if we are entering sftp mode
79+
fromUser = self.inq.get_nowait() #take the next char off the queue
80+
except Empty: pass
81+
else: #queue is not empty
82+
if (sftp or ord(fromUser) == 1) and not sftp_off: #if C-a is hit or the sftp flag is on
83+
sftp = False
84+
self.running = False #make the read buffer stop reading
85+
sys.stdout.flush()
86+
write('Opening sftp channel...',True)
87+
self.restoreInput() #turn back on echo, buffering, and signals
88+
SFTPChannel = self.open_sftp() #open a sftp channel
89+
if sftpHandle(SFTPChannel).run(self.inq): #go into sftp (true is quit, exit program, false is exit, return to ssh)
90+
SFTPChannel.close() #quit
91+
break
92+
SFTPChannel.close()
93+
self._defaultattrs = self.makeInputRaw() #go back to raw mode
94+
self.running = True
95+
self.inq.queue.clear()#clear the queue
96+
channel.send("\n")
97+
else:
98+
channel.send(fromUser) #send the user input to the server
99+
d=self.read_until(channel)
100+
if(d): #if the server had data, print it
101+
write(d)
102+
except Exception as e:
103+
write( "error: " + str(e),True)
104+
105+
'''
106+
Returns the size of the terminal
107+
'''
108+
def getWindowSize(self):
109+
return struct.unpack("HH",fcntl.ioctl(sys.stdout.fileno(), termios.TIOCGWINSZ, struct.pack("HH", 0, 0)))
110+
111+
'''
112+
makes the terminal input raw, no echo, buffering, or singals
113+
'''
114+
def makeInputRaw(self):
115+
#Get the file descriptor for standard in
116+
fd = sys.stdin.fileno()
117+
#Get the attributes belonging to the tty
118+
attrs = termios.tcgetattr(fd)
119+
oldattrs = attrs[::]
120+
#attrs[3] are the local modes - things like case, flush, and echo
121+
#& is bitwise AND operation, ~ is complement
122+
#termios.ECHO says to either echo or not echo a kteystroke, we don't want to
123+
attrs[3] = attrs[3] & ~termios.ECHO
124+
#termios.ICANON controls "canonical mode", which causes input to be buffered
125+
#by the terminal until something like EOL or EOF, we don't want that either.
126+
attrs[3] = attrs[3] & ~termios.ICANON
127+
attrs[3] = attrs[3] & ~termios.ISIG
128+
#Now we'll set tell the terminal to use the updated atttribute settings
129+
#termios.TCSANOW says to do it immediately, rather than waiting for the
130+
#current contents of the buffer to be handled.
131+
termios.tcsetattr(fd, termios.TCSANOW, attrs)
132+
return oldattrs
133+
134+
'''
135+
Return terminal settings to default
136+
'''
137+
def restoreInput(self):
138+
fd = sys.stdin.fileno()
139+
termios.tcsetattr(fd, termios.TCSANOW, self._defaultattrs)
140+
141+
'''
142+
catch every character
143+
'''
144+
def ioThreadFunc(self, fd, q):
145+
while 1:
146+
if self.running:
147+
q.put(fd.read(1))
148+
149+
'''
150+
read everything off of the network buffer
151+
'''
152+
def read_until(self, connect):
153+
data = ''
154+
while connect.recv_ready():
155+
data += connect.recv(4096)
156+
return data
157+
158+
'''
159+
Make sure the connection is closed on delete
160+
'''
161+
def __del__(self):
162+
self.close()
163+
164+
class sftpHandle():
165+
'''
166+
program loop for sftp mode, closes on exit
167+
'''
168+
def __init__(self,sftp):
169+
self.sftp = sftp
170+
171+
def run(self,inq):
172+
linep=['']
173+
try:
174+
sftpcmds["cd"].call(self.sftp,['.']) #we need to do a cd to the cwd do that paramiko knows where we are and can provide cwd
175+
sys.stdout.flush()
176+
#if cmd is empty or not exit, bye, or quit
177+
while len(linep) == 0 or (len(linep) > 0 and (not (linep[0] == "exit" or linep[0] == "bye" or linep[0] == "quit" ))) :
178+
#if the line was not empty
179+
if len(linep) > 0 and linep[0] != "":
180+
try:
181+
self.callSftpMethod(self.sftp,linep[0],linep[1:] if len(linep) > 1 else []) #try to call whatever cmd was provided
182+
except Exception as e:
183+
write(str(e),True)
184+
write("sftp "+str(self.sftp.getcwd())+">")#print prompt
185+
line = sys.stdin.readline() #read the line
186+
l = ''
187+
try:
188+
l = inq.get_nowait()#check if anything exists on the queue from ssh, should only happen the first time
189+
except Empty: pass
190+
linep = (l+line).strip('\n').split(' ') #remove new lines and split on spaces
191+
except Exception as e:
192+
write(e,True)
193+
write("Closing sftp channel...",True)
194+
return linep[0] == "quit" #if quit we will exit the program
195+
'''
196+
calls a sftp method from the sftpcmds dict
197+
'''
198+
def callSftpMethod(self,client,method,opts):
199+
try:
200+
if (not method == None) and method in sftpcmds:#if in cmds
201+
sftpcmds[method].call(client,opts)
202+
else: #if not print help
203+
write("Command does not exist",True)
204+
sftpcmds["help"].call(client,[])#print help
205+
except IndexError as e:
206+
write("incorrect parameter count: " + sftpcmds[method].help,True)
207+
208+
def write(message,line=False):
209+
sys.stdout.write(message + ('\n' * line))
210+
sys.stdout.flush()
211+
212+
def notNull(arg,default=None):
213+
return arg if arg != None else default
214+
215+
if __name__ == "__main__":
216+
#argument definitions
217+
parser = argparse.ArgumentParser(description='SSH Client')
218+
parser.add_argument('-o', metavar='host', type=str,
219+
help='host machine')
220+
parser.add_argument('--sftp', dest='sftp', action='store_true', help='start in sftp mode, does nothing if --stfp_off is present')
221+
parser.add_argument('-u', metavar='user', type=str )
222+
parser.add_argument('-p', metavar='password', type=str)
223+
parser.add_argument('-t', metavar='port', type=int, default=22)
224+
parser.add_argument('--sftp_off', action='store_true', help='disable toggle to sftp mode, default on. When off, C-a is freeded and passes through ssh')
225+
226+
args = parser.parse_args()
227+
host = notNull(args.o)
228+
user = notNull(args.u,os.environ['USER'])#take user env variable from user if not provided
229+
password = notNull(args.p)
230+
port = notNull(args.t)
231+
if not host:
232+
host = str(raw_input("Host: "))
233+
if not user:
234+
user = str(raw_input("User Name: "))
235+
if not password:
236+
password = getpass.getpass('Password: ')
237+
if args.sftp_off:
238+
print "sftp: OFF, remove --sftp_off from parameters to allow sftp mode toggle on C-a"
239+
c = sshClient(host,user,password,port,args.sftp,args.sftp_off)
240+
c.restoreInput() #make sure input is normal again

0 commit comments

Comments
 (0)