OLD | NEW |
(Empty) | |
| 1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.or
g/TR/xhtml1/DTD/xhtml1-transitional.dtd"> |
| 2 <html xmlns="http://www.w3.org/1999/xhtml"> |
| 3 <head> |
| 4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> |
| 5 <title>pyftpdlib Tutorial</title> |
| 6 </head> |
| 7 |
| 8 <body> |
| 9 <div id="wikicontent"> |
| 10 <h1><a name="1.0_-_Introduction" id="1.0_-_Introduction">1.0 - Introduction</a
></h1> |
| 11 <p><a name="1.0_-_Introduction" id="1.0_-_Introduction">pyftpdlib implements t
he server side of the FTP protocol as defined in </a><a href="http://www.faqs.or
g/rfcs/rfc959.html" rel="nofollow">RFC-959</a>. pyftpdlib consist of a single f
ile, <strong>ftpserver.py</strong>, which contains a hierarchy of classes, func
tions and variables which implement the backend functionality for the ftpd. Thi
s document is intended to serve as a simple <strong>API reference</strong> of m
ost important classes and functions. Also included is an introduction to <stron
g>customization</strong> through the use of some example scripts. </p> |
| 12 <p>If you have written a customized configuration you think could be useful
to the community feel free to share it by adding a comment at the end of this d
ocument. </p> |
| 13 <h1><a name="2.0_-_API_reference" id="2.0_-_API_reference">2.0 - API reference
</a></h1> |
| 14 <p><a name="2.0_-_API_reference" id="2.0_-_API_reference">function ftpserver.<
strong>log(</strong><em>msg</em><strong>)</strong> </a></p> |
| 15 <blockquote><a name="2.0_-_API_reference" id="2.0_-_API_reference">Log message
s intended for the end user. </a></blockquote> |
| 16 <hr /> |
| 17 <a name="2.0_-_API_reference" id="2.0_-_API_reference">function ftpserver.<str
ong>logline(</strong><em>msg</em><strong>)</strong> </a> |
| 18 <blockquote><a name="2.0_-_API_reference" id="2.0_-_API_reference">Log command
s and responses passing through the command channel. </a></blockquote> |
| 19 <hr /> |
| 20 <a name="2.0_-_API_reference" id="2.0_-_API_reference">function ftpserver.<str
ong>logerror(</strong><em>msg</em><strong>)</strong> </a> |
| 21 <blockquote><a name="2.0_-_API_reference" id="2.0_-_API_reference">Log traceba
ck outputs occurring in case of errors. </a></blockquote> |
| 22 <hr /> |
| 23 <a name="2.0_-_API_reference" id="2.0_-_API_reference">class ftpserver.<strong
>AuthorizerError</strong></a><strong><a href="http://code.google.com/p/pyftpdlib
/w/edit/AuthorizerError">?</a>()</strong> |
| 24 <blockquote>Base class for authorizers exceptions. </blockquote> |
| 25 <hr /> |
| 26 class ftpserver.<strong>DummyAuthorizer<a href="http://code.google.com/p/pyftp
dlib/w/edit/DummyAuthorizer">?</a>()</strong> |
| 27 <blockquote>Basic "dummy" authorizer class, suitable for subclassin
g to create your own custom authorizers. An "authorizer" is a class h
andling authentications and permissions of the FTP server. It is used inside FT
PHandler class for verifying user's password, getting users home directory, ch
ecking user permissions when a filesystem read/write event occurs and changing
user before accessing the filesystem. DummyAuthorizer is the base authorizer, p
roviding a platform independent interface for managing "virtual" FTP
users. Typically the first thing you have to do is create an instance of this c
lass and start adding ftp users: </blockquote> |
| 28 <pre>>>> from pyftpdlib import ftpserver<br />>>> authorizer
= ftpserver.DummyAuthorizer()<br />>>> authorizer.add_user('user', 'pa
ssword', '/home/user', perm='elradfmw')<br />>>> authorizer.add_anonymo
us('/home/nobody')</pre> |
| 29 <ul> |
| 30 <li><strong>add_user(</strong><em>username</em>, <em>password</em>, <em>home
dir</em><strong>[</strong>, <em>perm="elr"</em><strong>[</strong>, <em
>msg_login="Login successful."</em><strong>[</strong>, <em>msg_quit=&q
uot;Goodbye."</em><strong>]</strong><strong>]</strong><strong>])</strong> <
/li> |
| 31 <ul> |
| 32 <li>Add a user to the virtual users table. AuthorizerError exceptions rai
sed on error conditions such as insufficient permissions or duplicate usernames.
Optional perm argument is a set of letters referencing the user's permissions
(see the permission table shown below). Optional msg_login and msg_quit argumen
ts can be specified to provide customized response strings when user log-in and
quit. The perm argument of the add_user() method refers to user's permissions.
Every letter is used to indicate that the access rights the current FTP user h
as over the following specific actions are granted. </li> |
| 33 </ul> |
| 34 </ul> |
| 35 <blockquote>Read permissions: |
| 36 <ul> |
| 37 <li><strong>"e"</strong> = change directory (CWD command) </li> |
| 38 <li><strong>"l"</strong> = list files (LIST, NLST, MLSD commands
) </li> |
| 39 <li><strong>"r"</strong> = retrieve file from the server (RETR c
ommand) </li> |
| 40 </ul> |
| 41 Write permissions |
| 42 <ul> |
| 43 <li><strong>"a"</strong> = append data to an existing file (APPE
command) </li> |
| 44 <li><strong>"d"</strong> = delete file or directory (DELE, RMD c
ommands) </li> |
| 45 <li><strong>"f"</strong> = rename file or directory (RNFR, RNTO
commands) </li> |
| 46 <li><strong>"m"</strong> = create directory (MKD command) </li> |
| 47 <li><strong>"w"</strong> = store a file to the server (STOR, STO
U commands) </li> |
| 48 </ul> |
| 49 </blockquote> |
| 50 <ul> |
| 51 <li><strong>add_anonymous(</strong><em>homedir</em><strong>[</strong>, **<em
>kwargs</em><strong>])</strong> </li> |
| 52 <ul> |
| 53 <li>Add an anonymous user to the virtual users table. AuthorizerError ex
ception raised on error conditions such as insufficient permissions, missing ho
me directory, or duplicate anonymous users. The keyword arguments in kwargs are
the same expected by add_user() method: perm, msg_login and msg_quit. The opti
onal <em>perm</em> keyword argument is a string defaulting to "elr" re
ferencing "read-only" anonymous user's permission. Using a "writ
e" value results in a RuntimeWarning. </li> |
| 54 </ul> |
| 55 </ul> |
| 56 <ul> |
| 57 <li><strong>override_perm(</strong><em>directory</em>, <em>perm</em><strong>
[</strong>, <em>recursive=False</em><strong>])</strong> </li> |
| 58 <ul> |
| 59 <li>Override permissions for a given directory. <em><strong>New in 0.5.0</
strong></em> </li> |
| 60 </ul> |
| 61 </ul> |
| 62 <ul> |
| 63 <li><strong>validate_authentication(</strong><em>username</em>, <em>password
</em><strong>)</strong> </li> |
| 64 <ul> |
| 65 <li>Return True if the supplied username and password match the stored cre
dentials. </li> |
| 66 </ul> |
| 67 </ul> |
| 68 <ul> |
| 69 <li><strong>impersonate_user(</strong><em>username</em>, <em>password</em><s
trong>)</strong> </li> |
| 70 <ul> |
| 71 <li>Impersonate another user (noop). It is always called before accessing
the filesystem. By default it does nothing. The subclass overriding this meth
od is expected to provide a mechanism to change the current user. <em><strong>Ne
w in 0.4.0</strong></em> </li> |
| 72 </ul> |
| 73 </ul> |
| 74 <ul> |
| 75 <li><strong>terminate_impersonation(</strong><em>username</em>, <em>password
</em><strong>)</strong> </li> |
| 76 <ul> |
| 77 <li>Terminate impersonation (noop). It is always called after having acce
ssed the filesystem. By default it does nothing. The subclass overriding this
method is expected to provide a mechanism to switch back to the original user.
<em><strong>New in 0.4.0</strong></em> </li> |
| 78 </ul> |
| 79 </ul> |
| 80 <ul> |
| 81 <li><strong>remove_user(</strong><em>username</em><strong>)</strong> </li> |
| 82 <ul> |
| 83 <li>Remove a user from the virtual user table. </li> |
| 84 </ul> |
| 85 </ul> |
| 86 <hr /> |
| 87 <p>class ftpserver.<strong>FTPHandler(</strong><em>conn, server</em><strong>)<
/strong> </p> |
| 88 <blockquote>This class implements the FTP server Protocol Interpreter (see <a
href="http://www.faqs.org/rfcs/rfc959.html" rel="nofollow">RFC-959</a>), handli
ng commands received from the client on the control channel by calling the comm
and's corresponding method (e.g. for received command "MKD pathname",
ftp_MKD() method is called with pathname as the argument). All relevant sessio
n information are stored in instance variables. </blockquote> |
| 89 <blockquote>conn is the underlying socket object instance of the newly establi
shed connection, server is the FTPServer class instance. Basic usage simply req
uires creating an instance of FTPHandler class and specify which authorizer inst
ance it will going to use: </blockquote> |
| 90 <pre>>>> ftp_handler = ftpserver.FTPHandler<br />>>> ftp_han
dler.authorizer = authorizer</pre> |
| 91 <blockquote>All relevant session information is stored in class attributes re
produced below and can be modified before instantiating this class: </blockquot
e> |
| 92 <ul> |
| 93 <li><strong>timeout</strong> </li> |
| 94 <ul> |
| 95 <li>The timeout which is the maximum time a remote client may spend betwe
en FTP commands. If the timeout triggers, the remote client will be kicked off
(defaults to 300 seconds). <em><strong>New in 5.0</strong></em> </li> |
| 96 </ul> |
| 97 </ul> |
| 98 <ul> |
| 99 <li><strong>banner</strong> </li> |
| 100 <ul> |
| 101 <li>String sent when client connects (default "pyftpdlib %s ready.&qu
ot; %__ver__). </li> |
| 102 </ul> |
| 103 </ul> |
| 104 <ul> |
| 105 <li><strong>max_login_attempts</strong> </li> |
| 106 <ul> |
| 107 <li>Maximum number of wrong authentications before disconnecting (default
3). </li> |
| 108 </ul> |
| 109 </ul> |
| 110 <ul> |
| 111 <li><strong>permit_foreign_addresses</strong> </li> |
| 112 <ul> |
| 113 <li>Wether enable <a href="http://www.proftpd.org/docs/howto/FXP.html" rel
="nofollow">FXP</a> feature (default False). </li> |
| 114 </ul> |
| 115 </ul> |
| 116 <ul> |
| 117 <li><strong>permit_privileged_ports</strong> </li> |
| 118 <ul> |
| 119 <li>Set to True if you want to permit active connections (PORT) over privi
leged ports (not recommended, default False). </li> |
| 120 </ul> |
| 121 </ul> |
| 122 <ul> |
| 123 <li><strong>masquerade_address</strong> </li> |
| 124 <ul> |
| 125 <li>The "masqueraded" IP address to provide along PASV reply wh
en pyftpdlib is running behind a NAT or other types of gateways. When configure
d pyftpdlib will hide its local address and instead use the public address of
your NAT (default None). </li> |
| 126 </ul> |
| 127 </ul> |
| 128 <ul> |
| 129 <li><strong>passive_ports</strong> </li> |
| 130 <ul> |
| 131 <li>What ports ftpd will use for its passive data transfers. Value expect
ed is a list of integers (e.g. range(60000, 65535)). When configured pyftpdlib
will no longer use kernel-assigned random ports (default None). </li> |
| 132 </ul> |
| 133 </ul> |
| 134 <hr /> |
| 135 <p>class ftpserver.<strong>DTPHandler(</strong><em>sock_obj, cmd_channel</em><
strong>)</strong> </p> |
| 136 <blockquote>This class handles the server-data-transfer-process (server-DTP, s
ee <a href="http://www.faqs.org/rfcs/rfc959.html" rel="nofollow">RFC-959</a>) ma
naging all transfer operations regarding the data channel. </blockquote> |
| 137 <blockquote>sock_obj is the underlying socket object instance of the newly est
ablished connection, cmd_channel is the FTPHandler class instance. Unless you w
ant to add extra functionalities like bandwidth throttling you shouldn't be int
erested in putting hands on this class. </blockquote> |
| 138 <ul> |
| 139 <li><strong>timeout</strong> </li> |
| 140 <ul> |
| 141 <li>The timeout which roughly is the maximum time we permit data transfer
s to stall for with no progress. If the timeout triggers, the remote client wi
ll be kicked off. <em><strong>New in 5.0</strong></em> </li> |
| 142 </ul> |
| 143 </ul> |
| 144 <ul> |
| 145 <li><strong>cmd_channel</strong> </li> |
| 146 <ul> |
| 147 <li>The command channel class instance. </li> |
| 148 </ul> |
| 149 </ul> |
| 150 <ul> |
| 151 <li><strong>file_obj</strong> </li> |
| 152 <ul> |
| 153 <li>The file transferred (if any). </li> |
| 154 </ul> |
| 155 </ul> |
| 156 <ul> |
| 157 <li><strong>receive</strong> </li> |
| 158 <ul> |
| 159 <li>True if channel is used for receiving data. </li> |
| 160 </ul> |
| 161 </ul> |
| 162 <ul> |
| 163 <li><strong>transfer_finished</strong> </li> |
| 164 <ul> |
| 165 <li>True if transfer completed successfully. </li> |
| 166 </ul> |
| 167 </ul> |
| 168 <ul> |
| 169 <li><strong>get_transmitted_bytes()</strong> </li> |
| 170 <ul> |
| 171 <li>Return the number of transmitted bytes. </li> |
| 172 </ul> |
| 173 </ul> |
| 174 <ul> |
| 175 <li><strong>transfer_in_progress()</strong> </li> |
| 176 <ul> |
| 177 <li>Return True if a transfer is in progress. </li> |
| 178 </ul> |
| 179 </ul> |
| 180 <ul> |
| 181 <li><strong>enable_receiving(</strong><em>type</em><strong>)</strong> </li> |
| 182 <ul> |
| 183 <li>Enable receiving of data over the channel. Depending on the type curr
ently in use it creates an appropriate wrapper for the incoming data. </li> |
| 184 </ul> |
| 185 </ul> |
| 186 <ul> |
| 187 <li><strong>push(</strong><em>data</em><strong>)</strong> </li> |
| 188 <ul> |
| 189 <li>Push a bufferable <em>data</em> object (e.g. a string) onto the deque
and initiate send. </li> |
| 190 </ul> |
| 191 </ul> |
| 192 <ul> |
| 193 <li><strong>push_with_producer(</strong><em>producer</em><strong>)</strong>
</li> |
| 194 <ul> |
| 195 <li>Push data using a producer and initiate send. </li> |
| 196 </ul> |
| 197 </ul> |
| 198 <hr /> |
| 199 <p>class ftpserver.<strong>FTPServer(</strong><em>address, handler</em><strong
>)</strong> </p> |
| 200 <blockquote>This class is an asyncore.dispatcher subclass. It creates a FTP s
ocket listening on address (a tuple containing the ip:port pair), dispatching th
e requests to a "handler" (typically FTPHandler class object). It is
typically used for starting asyncore polling loop: </blockquote> |
| 201 <pre>>>> address = ('127.0.0.1', 21)<br />>>> ftpd = ftpserv
er.FTPServer(address, ftp_handler)<br />>>> ftpd.serve_forever()</pre> |
| 202 <ul> |
| 203 <li><strong>max_cons</strong> </li> |
| 204 <ul> |
| 205 <li>Number of maximum simultaneous connections accepted (default 0 == <em>
no limit</em>). </li> |
| 206 </ul> |
| 207 </ul> |
| 208 <ul> |
| 209 <li><strong>max_cons_per_ip</strong> </li> |
| 210 <ul> |
| 211 <li>Number of maximum connections accepted for the same IP address (defaul
t 0 == <em>no limit</em>). </li> |
| 212 </ul> |
| 213 </ul> |
| 214 <ul> |
| 215 <li><strong>serve_forever([</strong><em>timeout=1</em><strong>[</strong>, <e
m>use_poll=False</em><strong>[</strong>, <em>map=None</em><strong>[</strong>, <e
m>count=None</em><strong>]]])</strong> </li> |
| 216 <ul> |
| 217 <li>A wrap around asyncore.loop(); starts the asyncore polling loop inclu
ding running the scheduler. The arguments are the same expected by original as
yncore.loop() function. </li> |
| 218 </ul> |
| 219 </ul> |
| 220 <ul> |
| 221 <li><strong>close()</strong> </li> |
| 222 <ul> |
| 223 <li>Stop serving without disconnecting currently connected clients. </li> |
| 224 </ul> |
| 225 </ul> |
| 226 <ul> |
| 227 <li><strong>close_all([</strong><em>map=None</em><strong>[</strong>, <em>ign
ore_all=False</em><strong>]])</strong> </li> |
| 228 <ul> |
| 229 <li>Stop serving disconnecting also the currently connected clients. The m
ap parameter is a dictionary whose items are the channels to close. If map is om
itted, the default asyncore.socket_map is used. Having ignore_all parameter set
to False results in raising exception in case of unexpected errors. </li> |
| 230 </ul> |
| 231 </ul> |
| 232 <hr /> |
| 233 <p>class ftpserver.<strong>AbstractedFS()</strong> </p> |
| 234 <blockquote>A class used to interact with the file system, providing a high l
evel, cross-platform interface compatible with both Windows and UNIX style fil
esystems. It provides some utility methods to operate on pathnames and the wrap
s around the common calls to interact with the filesystem (e.g. open(), os.mkdi
r(), os.listdir(), etc...). These latter ones are not reproduced below (see the
source instead). </blockquote> |
| 235 <ul> |
| 236 <li><strong>root</strong> </li> |
| 237 <ul> |
| 238 <li>User's home directory ("real"). </li> |
| 239 </ul> |
| 240 </ul> |
| 241 <ul> |
| 242 <li><strong>cwd</strong> </li> |
| 243 <ul> |
| 244 <li>User's current working directory ("virtual"). </li> |
| 245 </ul> |
| 246 </ul> |
| 247 <ul> |
| 248 <li><strong>ftpnorm(</strong><em>ftppath</em><strong>)</strong> </li> |
| 249 <ul> |
| 250 <li>Normalize a "virtual" ftp pathname depending on the current
working directory (e.g. having "/foo" as current working directory &
quot;x" becomes "/foo/x"). <em><strong>New in 3.0</strong></em> <
/li> |
| 251 </ul> |
| 252 </ul> |
| 253 <ul> |
| 254 <li><strong>ftp2fs(</strong><em>ftppath</em><strong>)</strong> </li> |
| 255 <ul> |
| 256 <li>Translate a "virtual" ftp pathname into equivalent absolute
"real" filesystem pathname (e.g. having "/home/user" as ro
ot directory "x" becomes "/home/user/x"). <em><strong>New i
n 3.0</strong></em> </li> |
| 257 </ul> |
| 258 </ul> |
| 259 <ul> |
| 260 <li><strong>fs2ftp(</strong><em>fspath</em><strong>)</strong> </li> |
| 261 <ul> |
| 262 <li>Translate a "real" filesystem pathname into equivalent abso
lute "virtual" ftp pathname depending on the user's root directory (e
.g. having "/home/user" as root directory "/home/user/x" be
comes "/x". <em><strong>New in 3.0</strong></em> </li> |
| 263 </ul> |
| 264 </ul> |
| 265 <ul> |
| 266 <li><strong>validpath(</strong><em>path</em><strong>)</strong> </li> |
| 267 <ul> |
| 268 <li>Check whether the path belongs to user's home directory. Expected arg
ument is a "real" filesystem path. If path is a symbolic link it is r
esolved to check its real destination. Pathnames escaping from user's root dir
ectory are considered not valid (return False). </li> |
| 269 </ul> |
| 270 </ul> |
| 271 <hr /> |
| 272 <p>class ftpserver.<strong>CallLater<a href="http://code.google.com/p/pyftpdli
b/w/edit/CallLater">?</a>(</strong><em>seconds</em>, <em>target</em> <strong>[</
strong>, *<em>args</em> <strong>[</strong>, **<em>kwargs</em><strong>]])</strong
> </p> |
| 273 <blockquote>Calls a function at a later time. It can be used to asynchronousl
y schedule a call within the polling loop without blocking it. The instance ret
urned is an object that can be used to cancel or reschedule the call. <em><stro
ng>New in 0.5.0</strong></em> </blockquote> |
| 274 <ul> |
| 275 <li><strong>cancelled</strong> </li> |
| 276 <ul> |
| 277 <li>Whether the call has been cancelled. </li> |
| 278 </ul> |
| 279 </ul> |
| 280 <ul> |
| 281 <li><strong>reset()</strong> </li> |
| 282 <ul> |
| 283 <li>Reschedule the call resetting the current countdown. </li> |
| 284 </ul> |
| 285 </ul> |
| 286 <ul> |
| 287 <li><strong>delay(</strong><em>seconds</em><strong>)</strong> </li> |
| 288 <ul> |
| 289 <li>Reschedule the call for a later time. </li> |
| 290 </ul> |
| 291 </ul> |
| 292 <ul> |
| 293 <li><strong>cancel()</strong> </li> |
| 294 <ul> |
| 295 <li>Unschedule the call. </li> |
| 296 </ul> |
| 297 </ul> |
| 298 <h1></h1> |
| 299 <h1><a name="3.0_-_Customizing_your_FTP_server" id="3.0_-_Customizing_your_FTP
_server">3.0 - Customizing your FTP server</a></h1> |
| 300 <p><a name="3.0_-_Customizing_your_FTP_server" id="3.0_-_Customizing_your_FTP_
server">Below is a set of example scripts showing some of the possible customiz
ations that can be done with pyftpdlib. Some of them are included in demo dire
ctory of pyftpdlib source distribution. </a></p> |
| 301 <h3><a name="3.1_-_Building_a_Base_FTP_server" id="3.1_-_Building_a_Base_FTP_s
erver">3.1 - Building a Base FTP server</a></h3> |
| 302 <p><a name="3.1_-_Building_a_Base_FTP_server" id="3.1_-_Building_a_Base_FTP_se
rver">The script below is a basic configuration, and it's probably the best st
arting point for understanding how things work. It uses the base DummyAuthorizer
for adding a bunch of "virtual" users. </a></p> |
| 303 <p><a name="3.1_-_Building_a_Base_FTP_server" id="3.1_-_Building_a_Base_FTP_se
rver">It also sets a limit for connections by overriding FTPServer.max_cons and
FTPServer.max_cons_per_ip attributes which are intended to set limits for maxim
um connections to handle simultaneously and maximum connections from the same I
P address. Overriding these variables is always a good idea (they default to 0,
or "no limit") since they are a good workaround for avoiding DoS atta
cks. </a></p> |
| 304 <pre><a name="3.1_-_Building_a_Base_FTP_server" id="3.1_-_Building_a_Base_FTP_
server">#!/usr/bin/env python<br /># basic_ftpd.py<br /><br />"""
A basic FTP server which uses a DummyAuthorizer for managing 'virtual<br />users
', setting a limit for incoming connections.<br />"""<br /><br />
import os<br /><br />from pyftpdlib import ftpserver<br /><br /><br />if __name_
_ == "__main__":<br /><br /> # Instantiate a dummy authorizer for m
anaging 'virtual' users<br /> authorizer = ftpserver.DummyAuthorizer()<br /><
br /> # Define a new user having full r/w permissions and a read-only<br />
# anonymous user<br /> authorizer.add_user('user', '12345', os.getcwd(), pe
rm='elradfmw')<br /> authorizer.add_anonymous(os.getcwd())<br /><br /> # I
nstantiate FTP handler class<br /> ftp_handler = ftpserver.FTPHandler<br />
ftp_handler.authorizer = authorizer<br /><br /> # Define a customized banne
r (string returned when client connects)<br /> ftp_handler.banner = "pyf
tpdlib %s based ftpd ready." %ftpserver.__ver__<br /><br /> # Specify a
masquerade address and the range of ports to use for<br /> # passive connecti
ons. Decomment in case you're behind a NAT.<br /> #ftp_handler.masquerade_ad
dress = '151.25.42.11'<br /> #ftp_handler.passive_ports = range(60000, 65535)
<br /><br /> # Instantiate FTP server class and listen to 0.0.0.0:21<br />
address = ('', 21)<br /> ftpd = ftpserver.FTPServer(address, ftp_handler)<br
/><br /> # set a limit for connections<br /> ftpd.max_cons = 256<br />
ftpd.max_cons_per_ip = 5<br /><br /> # start ftp server<br /> ftpd.serve_
forever()</a></pre> |
| 305 <h3><a name="3.2_-_Logging_management" id="3.2_-_Logging_management">3.2 - Log
ging management</a></h3> |
| 306 <p><a name="3.2_-_Logging_management" id="3.2_-_Logging_management">As mention
ed, ftpserver.py comes with 3 different functions intended for a separate loggin
g system: log(), logline() and logerror(). Let's suppose you don't want to print
FTPd messages on screen but you want to write them into different files: <em>&q
uot;/var/log/ftpd.log"</em> will be main log file, <em>"/var/log/ftpd.
lines.log"</em> the one where you'll want to store commands and responses p
assing through the control connection. </a></p> |
| 307 <p><a name="3.2_-_Logging_management" id="3.2_-_Logging_management">Here's one
method this could be implemented: </a></p> |
| 308 <pre><a name="3.2_-_Logging_management" id="3.2_-_Logging_management">#!/usr/b
in/env python<br /># logging_management.py<br /><br />import os<br />import time
<br /><br />from pyftpdlib import ftpserver<br /><br />now = lambda: time.strfti
me("[%Y-%b-%d %H:%M:%S]")<br /><br />def standard_logger(msg):<br />
f1.write("%s %s\n" %(now(), msg))<br /><br />def line_logger(msg):<b
r /> f2.write("%s %s\n" %(now(), msg))<br /><br />if __name__ == &q
uot;__main__":<br /> f1 = open('ftpd.log', 'a')<br /> f2 = open('ftpd
.lines.log', 'a')<br /> ftpserver.log = standard_logger<br /> ftpserver.lo
gline = line_logger<br /><br /> authorizer = ftpserver.DummyAuthorizer()<br /
> authorizer.add_anonymous(os.getcwd())<br /> ftp_handler = ftpserver.FTPH
andler<br /> ftp_handler.authorizer = authorizer<br /> address = ('', 21)<
br /> ftpd = ftpserver.FTPServer(address, ftp_handler)<br /> ftpd.serve_fo
rever()</a></pre> |
| 309 <h3><a name="3.3_-_Storing_passwords_as_hash_digests" id="3.3_-_Storing_passwo
rds_as_hash_digests">3.3 - Storing passwords as hash digests</a></h3> |
| 310 <p><a name="3.3_-_Storing_passwords_as_hash_digests" id="3.3_-_Storing_passwor
ds_as_hash_digests">Using FTP server library with the default DummyAuthorizer m
eans that password will be stored in clear-text. An end-user ftpd using the def
ault dummy authorizer would typically require a configuration file for authenti
cating users and their passwords but storing clear-text passwords is of course
undesirable. </a></p> |
| 311 <p><a name="3.3_-_Storing_passwords_as_hash_digests" id="3.3_-_Storing_passwor
ds_as_hash_digests">The most common way to do things in such case would be firs
t creating new users and then storing their usernames + passwords as hash diges
ts into a file or wherever you find it convenient. </a></p> |
| 312 <p><a name="3.3_-_Storing_passwords_as_hash_digests" id="3.3_-_Storing_passwor
ds_as_hash_digests">The example below shows how to easily create an encrypted a
ccount storage system by storing passwords as one-way hashes by using md5 algor
ithm. This could be easily done by using the <strong>md5</strong> module includ
ed with Python stdlib and by sub-classing the original DummyAuthorizer class ove
rriding its validate_authentication() method: </a></p> |
| 313 <pre><a name="3.3_-_Storing_passwords_as_hash_digests" id="3.3_-_Storing_passw
ords_as_hash_digests">#!/usr/bin/env python<br /># md5_ftpd.py<br /><br />"
""A basic ftpd storing passwords as hash digests (platform independent
).<br />"""<br /><br />import md5<br />import os<br /><br />from
pyftpdlib import ftpserver<br /><br /><br />class DummyMD5Authorizer(ftpserver.D
ummyAuthorizer):<br /><br /> def validate_authentication(self, username, pass
word):<br /> hash = md5.new(password).hexdigest()<br /> return sel
f.user_table[username]['pwd'] == hash<br /><br />if __name__ == "__main__&q
uot;:<br /> # get a hash digest from a clear-text password<br /> hash = md
5.new('12345').hexdigest()<br /> authorizer = DummyMD5Authorizer()<br /> a
uthorizer.add_user('user', hash, os.getcwd(), perm='elradfmw')<br /> authoriz
er.add_anonymous(os.getcwd())<br /> ftp_handler = ftpserver.FTPHandler<br />
ftp_handler.authorizer = authorizer<br /> address = ('', 21)<br /> ftpd
= ftpserver.FTPServer(address, ftp_handler)<br /> ftpd.serve_forever()</a></
pre> |
| 314 <h3><a name="3.4_-_Unix_FTP_Server" id="3.4_-_Unix_FTP_Server">3.4 - Unix FTP
Server</a></h3> |
| 315 <p><a name="3.4_-_Unix_FTP_Server" id="3.4_-_Unix_FTP_Server">If you're runnin
g a Unix system you may want to configure your ftpd to include support for "
;real" users existing on the system. </a></p> |
| 316 <p><a name="3.4_-_Unix_FTP_Server" id="3.4_-_Unix_FTP_Server">The example belo
w shows how to use </a><a href="http://docs.python.org/lib/module-pwd.html" rel=
"nofollow">pwd</a> and <a href="http://docs.python.org/lib/module-spwd.html" rel
="nofollow">spwd</a> modules available in <em>Python 2.5</em> or greater (UNIX s
ystems only) to interact with UNIX user account and shadow passwords database a
nd also to automatically get the user's home directory. </p> |
| 317 <p>impersonate_user() and terminate_impersonation() methods of the dummy auth
orizer are overridden to provide the proper mechanism to reflect the current lo
gged-in user every time he's going to access the filesystem. </p> |
| 318 <p>Note that the users you're going to add through the add_user method must al
ready exist on the system. </p> |
| 319 <pre>#!/usr/bin/env python<br /># unix_ftpd.py<br /><br />"""A
ftpd using local unix account database to authenticate users<br />(users must al
ready exist).<br /><br />It also provides a mechanism to (temporarily) impersona
te the system<br />users every time they are going to perform filesystem operati
ons.<br />"""<br /><br />import os<br />import pwd, spwd, crypt<b
r /><br />from pyftpdlib import ftpserver<br /><br /><br />class UnixAuthorizer(
ftpserver.DummyAuthorizer):<br /><br /> # the uid/gid the daemon runs under<b
r /> PROCESS_UID = os.getuid()<br /> PROCESS_GID = os.getgid()<br /><br />
def add_user(self, username, homedir=None, **kwargs):<br /> "&qu
ot;"Add a "real" system user to the virtual users table.<br /><br
/> If no home argument is specified the user's home directory will<br />
be used.<br /><br /> The keyword arguments in kwargs are the same
expected by the<br /> original add_user method: "perm", "
msg_login" and "msg_quit".<br /> """<br />
# get the list of all available users on the system and check<br />
# if provided username exists<br /> users = [entry.pw_name for entry in
pwd.getpwall()]<br /> if not username in users:<br /> raise f
tpserver.AuthorizerError('No such user "%s".' %username)<br />
if not homedir:<br /> homedir = pwd.getpwnam(username).pw_dir<br />
ftpserver.DummyAuthorizer.add_user(self, username, '', homedir,**kwargs)<b
r /><br /> def add_anonymous(self, homedir=None, realuser="nobody",
**kwargs):<br /> """Add an anonymous user to the virtual
users table.<br /><br /> If no homedir argument is specified the realuser
's home<br /> directory will possibly be determined and used.<br /><br />
realuser argument specifies the system user to use for managing<br />
anonymous sessions. On many UNIX systems "nobody" is tipically<b
r /> used but it may change (e.g. "ftp").<br /> "&q
uot;"<br /> users = [entry.pw_name for entry in pwd.getpwall()]<br /
> if not realuser in users:<br /> raise ftpserver.AuthorizerEr
ror('No such user "%s".' %realuser)<br /> if not homedir:<br />
homedir = pwd.getpwnam(realuser).pw_dir<br /> ftpserver.Dummy
Authorizer.add_anonymous(self, homedir, **kwargs)<br /> self.anon_user =
realuser<br /><br /> def validate_authentication(self, username, password):<b
r /> if (username == "anonymous") and self.has_user('anonymous'
):<br /> username = self.anon_user<br /><br /> pw1 = spwd.gets
pnam(username).sp_pwd<br /> pw2 = crypt.crypt(password, pw1)<br />
return pw1 == pw2<br /><br /> def impersonate_user(self, username, password)
:<br /> if (username == "anonymous") and self.has_user('anonymo
us'):<br /> username = self.anon_user<br /> uid = pwd.getpwnam
(username).pw_uid<br /> gid = pwd.getpwnam(username).pw_gid<br />
os.setegid(gid)<br /> os.seteuid(uid)<br /><br /> def terminate_impers
onation(self):<br /> os.setegid(self.PROCESS_GID)<br /> os.seteuid
(self.PROCESS_UID)<br /><br /><br />if __name__ == "__main__":<br />
authorizer = UnixAuthorizer()<br /> # add a user (note: user must already e
xists)<br /> authorizer.add_user('user', perm='elradfmw')<br /> authorizer
.add_anonymous(os.getcwd())<br /> ftp_handler = ftpserver.FTPHandler<br />
ftp_handler.authorizer = authorizer<br /> address = ('', 21)<br /> ftpd =
ftpserver.FTPServer(address, ftp_handler)<br /> ftpd.serve_forever()</pre> |
| 320 <h3><a name="3.5_-_Windows_NT_FTP_Server" id="3.5_-_Windows_NT_FTP_Server">3.5
- Windows NT FTP Server</a></h3> |
| 321 <p><a name="3.5_-_Windows_NT_FTP_Server" id="3.5_-_Windows_NT_FTP_Server">The
following code shows how to implement a basic authorizer for a Windows NT work
station to authenticate against existing Windows user accounts. This code uses
Mark Hammond's </a><a href="http://starship.python.net/crew/mhammond/win32/" rel
="nofollow">pywin32</a> extension which is required to be installed previously.
</p> |
| 322 <p>Note that, as for UNIX authorizer, the users you're going to add through th
e add_user method must already exist on the system. </p> |
| 323 <pre>#!/usr/bin/env python<br /># winnt_ftpd.py<br /><br />"""A
ftpd using local Windows NT account database to authenticate users<br />(users
must already exist).<br /><br />It also provides a mechanism to (temporarily) im
personate the system<br />users every time they are going to perform filesystem
operations.<br />"""<br /><br />import os<br />import win32securi
ty, win32net, pywintypes, win32con<br /><br />from pyftpdlib import ftpserver<br
/><br /><br />def get_profile_dir(username):<br /> """Return
the user's profile directory."""<br /> import _winreg, win32ap
i<br /> sid = win32security.ConvertSidToStringSid(<br /> win32secu
rity.LookupAccountName(None, username)[0])<br /> try:<br /> key = _win
reg.OpenKey(_winreg.HKEY_LOCAL_MACHINE,<br /> r"SOFTWARE\Microsoft
\Windows NT\CurrentVersion\ProfileList"+"\\"+sid)<br /> except
WindowsError:<br /> raise ftpserver.AuthorizerError("No profile dir
ectory defined for %s "<br /> "
user" %username)<br /> value = _winreg.QueryValueEx(key, "ProfileIm
agePath")[0]<br /> return win32api.ExpandEnvironmentStrings(value)<br />
<br /><br />class WinNtAuthorizer(ftpserver.DummyAuthorizer):<br /><br /> def
add_user(self, username, homedir=None, **kwargs):<br /> ""&quo
t;Add a "real" system user to the virtual users table.<br /><br />
If no homedir argument is specified the user's profile<br /> director
y will possibly be determined and used.<br /><br /> The keyword arguments
in kwargs are the same expected by the<br /> original add_user method: &
quot;perm", "msg_login" and "msg_quit".<br /> &q
uot;""<br /> # get the list of all available users on the syste
m and check<br /> # if provided username exists<br /> users = [ent
ry['name'] for entry in win32net.NetUserEnum(None, 0)[0]]<br /> if not us
ername in users:<br /> raise ftpserver.AuthorizerError('No such user
"%s".' %username)<br /> if not homedir:<br /> homedi
r = get_profile_dir(username)<br /> ftpserver.DummyAuthorizer.add_user(se
lf, username, '', homedir,<br /> **kwa
rgs)<br /><br /> def add_anonymous(self, homedir=None, realuser="Guest&q
uot;,<br /> password="", **kwargs):<br />
"""Add an anonymous user to the virtual users table.<br /><br />
If no homedir argument is specified the realuser's profile<br /> d
irectory will possibly be determined and used.<br /><br /> realuser and p
assword arguments are the credentials to use for<br /> managing anonymous
sessions.<br /> The same behaviour is followed in IIS where the Guest ac
count<br /> is used to do so (note: it must be enabled first).<br />
"""<br /> users = [entry['name'] for entry in win32net.
NetUserEnum(None, 0)[0]]<br /> if not realuser in users:<br />
raise ftpserver.AuthorizerError('No such user "%s".' %realuser)<br />
if not homedir:<br /> homedir = get_profile_dir(realuser)<br
/> # make sure provided credentials are valid, otherwise an exception<br
/> # will be thrown; to do so we actually try to impersonate the user<br
/> self.impersonate_user(realuser, password)<br /> self.terminate_
impersonation()<br /> ftpserver.DummyAuthorizer.add_anonymous(self, homed
ir, **kwargs)<br /> self.anon_user = realuser<br /> self.anon_pwd
= password<br /><br /> def validate_authentication(self, username, password):
<br /> if (username == "anonymous") and self.has_user('anonymou
s'):<br /> username = self.anon_user<br /> password = self
.anon_pwd<br /> try:<br /> win32security.LogonUser(username, N
one, password,<br /> win32con.LOGON32_LOGON_INTERACTIVE,<br />
win32con.LOGON32_PROVIDER_DEFAULT)<br /> return True<br
/> except pywintypes.error:<br /> return False<br /><br />
def impersonate_user(self, username, password):<br /> if (username == &qu
ot;anonymous") and self.has_user('anonymous'):<br /> username =
self.anon_user<br /> password = self.anon_pwd<br /> handler =
win32security.LogonUser(username, None, password,<br /> win
32con.LOGON32_LOGON_INTERACTIVE,<br /> win32con.LOGON32_PRO
VIDER_DEFAULT)<br /> win32security.ImpersonateLoggedOnUser(handler)<br />
handler.Close()<br /><br /> def terminate_impersonation(self):<br />
win32security.RevertToSelf()<br /><br /><br />if __name__ == "__main
__":<br /> authorizer = WinNtAuthorizer()<br /> # add a user (note: u
ser must already exists)<br /> authorizer.add_user('user', perm='elradfmw')<b
r /> # add an anonymous user using Guest account to handle the anonymous<br /
> # sessions (note: Guest must be enabled first)<br /> authorizer.add_anon
ymous(os.getcwd())<br /> ftp_handler = ftpserver.FTPHandler<br /> ftp_hand
ler.authorizer = authorizer<br /> address = ('', 21)<br /> ftpd = ftpserve
r.FTPServer(address, ftp_handler)<br /> ftpd.serve_forever() |
| 324 |
| 325 |
| 326 </pre> |
| 327 </div> |
| 328 </body> |
| 329 </html> |
OLD | NEW |