vsftpd-3.0.3/0000751000175000017500000000000012554567073012037 5ustar chrischrisvsftpd-3.0.3/privops.h0000644000175000017500000000571711350333672013715 0ustar chrischris#ifndef VSF_PRIVOPS_H #define VSF_PRIVOPS_H struct mystr; struct vsf_session; /* vsf_privop_get_ftp_port_sock() * PURPOSE * Return a network socket potentially bound to a privileged port (less than * 1024) and connected to the remote. * PARAMETERS * p_sess - the current session object * remote_port - the remote port to connect to * use_port_sockaddr - true if we should use the specific sockaddr for connect * RETURNS * A file descriptor which is a socket bound to the privileged port, and * connected to the remote on the specified port. * Kills the process / session if the bind() fails. * Returns -1 if the bind() worked but the connect() was not possible. */ int vsf_privop_get_ftp_port_sock(struct vsf_session* p_sess, unsigned short remote_port, int use_port_sockaddr); /* vsf_privop_pasv_cleanup() * PURPOSE * Makes sure any listening passive socket is closed. * PARAMETERS * p_sess - the current session object */ void vsf_privop_pasv_cleanup(struct vsf_session* p_sess); /* vsf_privop_pasv_listen() * PURPOSE * Start listening for an FTP data connection. * PARAMETERS * p_sess - the current session object * RETURNS * The port we ended up listening on. */ unsigned short vsf_privop_pasv_listen(struct vsf_session* p_sess); /* vsf_privop_pasv_active() * PURPOSE * Determine whether there is a passive listening socket active. * PARAMETERS * p_sess - the current session object * RETURNS * 1 if active, 0 if not. */ int vsf_privop_pasv_active(struct vsf_session* p_sess); /* vsf_privop_accept_pasv() * PURPOSE * Accept a connection on the listening data socket. * PARAMETERS * p_sess - the current session object * RETURNS * The file descriptor of the accepted incoming connection; or -1 if a * network error occurred or -2 if the incoming connection was from the * wrong IP (security issue). */ int vsf_privop_accept_pasv(struct vsf_session* p_sess); /* vsf_privop_do_file_chown() * PURPOSE * Takes a file owned by the unprivileged FTP user, and change the ownership * to the value defined in the config file. * PARAMETERS * p_sess - the current session object * fd - the file descriptor of the regular file */ void vsf_privop_do_file_chown(struct vsf_session* p_sess, int fd); enum EVSFPrivopLoginResult { kVSFLoginNull = 0, kVSFLoginFail, kVSFLoginAnon, kVSFLoginReal }; /* vsf_privop_do_login() * PURPOSE * Check if the supplied username/password combination is valid. This * interface caters for checking both anonymous and real logins. * PARAMETERS * p_sess - the current session object * p_pass_str - the proposed password * RETURNS * kVSFLoginFail - access denied * kVSFLoginAnon - anonymous login credentials OK * kVSFLoginReal - real login credentials OK */ enum EVSFPrivopLoginResult vsf_privop_do_login( struct vsf_session* p_sess, const struct mystr* p_pass_str); #endif /* VSF_PRIVOPS_H */ vsftpd-3.0.3/ftppolicy.h0000644000175000017500000000072111122365674014216 0ustar chrischris#ifndef VSF_FTPPOLICY_H #define VSF_FTPPOLICY_H /* Forward delcarations */ struct pt_sandbox; struct vsf_session; /* policy_setup() * PURPOSE * Sets up a sandbox policy according to the currently enabled options. * PARAMETERS * p_sandbox - the sandbox to set the policy on * p_sess - current vsftpd session object */ void policy_setup(struct pt_sandbox* p_sandbox, const struct vsf_session* p_sess); #endif /* VSF_FTPPOLICY_H */ vsftpd-3.0.3/vsftpd.conf.50000644000175000017500000010303411266751406014356 0ustar chrischris.TH VSFTPD.CONF 5 .SH NAME vsftpd.conf \- config file for vsftpd .SH DESCRIPTION vsftpd.conf may be used to control various aspects of vsftpd's behaviour. By default, vsftpd looks for this file at the location .BR /etc/vsftpd.conf . However, you may override this by specifying a command line argument to vsftpd. The command line argument is the pathname of the configuration file for vsftpd. This behaviour is useful because you may wish to use an advanced inetd such as .BR xinetd to launch vsftpd with different configuration files on a per virtual host basis. .SH FORMAT The format of vsftpd.conf is very simple. Each line is either a comment or a directive. Comment lines start with a # and are ignored. A directive line has the format: option=value It is important to note that it is an error to put any space between the option, = and value. Each setting has a compiled in default which may be modified in the configuration file. .SH BOOLEAN OPTIONS Below is a list of boolean options. The value for a boolean option may be set to .BR YES or .BR NO . .TP .B allow_anon_ssl Only applies if .BR ssl_enable is active. If set to YES, anonymous users will be allowed to use secured SSL connections. Default: NO .TP .B anon_mkdir_write_enable If set to YES, anonymous users will be permitted to create new directories under certain conditions. For this to work, the option .BR write_enable must be activated, and the anonymous ftp user must have write permission on the parent directory. Default: NO .TP .B anon_other_write_enable If set to YES, anonymous users will be permitted to perform write operations other than upload and create directory, such as deletion and renaming. This is generally not recommended but included for completeness. Default: NO .TP .B anon_upload_enable If set to YES, anonymous users will be permitted to upload files under certain conditions. For this to work, the option .BR write_enable must be activated, and the anonymous ftp user must have write permission on desired upload locations. This setting is also required for virtual users to upload; by default, virtual users are treated with anonymous (i.e. maximally restricted) privilege. Default: NO .TP .B anon_world_readable_only When enabled, anonymous users will only be allowed to download files which are world readable. This is recognising that the ftp user may own files, especially in the presence of uploads. Default: YES .TP .B anonymous_enable Controls whether anonymous logins are permitted or not. If enabled, both the usernames .BR ftp and .BR anonymous are recognised as anonymous logins. Default: YES .TP .B ascii_download_enable When enabled, ASCII mode data transfers will be honoured on downloads. Default: NO .TP .B ascii_upload_enable When enabled, ASCII mode data transfers will be honoured on uploads. Default: NO .TP .B async_abor_enable When enabled, a special FTP command known as "async ABOR" will be enabled. Only ill advised FTP clients will use this feature. Additionally, this feature is awkward to handle, so it is disabled by default. Unfortunately, some FTP clients will hang when cancelling a transfer unless this feature is available, so you may wish to enable it. Default: NO .TP .B background When enabled, and vsftpd is started in "listen" mode, vsftpd will background the listener process. i.e. control will immediately be returned to the shell which launched vsftpd. Default: NO .TP .B check_shell Note! This option only has an effect for non-PAM builds of vsftpd. If disabled, vsftpd will not check /etc/shells for a valid user shell for local logins. Default: YES .TP .B chmod_enable When enabled, allows use of the SITE CHMOD command. NOTE! This only applies to local users. Anonymous users never get to use SITE CHMOD. Default: YES .TP .B chown_uploads If enabled, all anonymously uploaded files will have the ownership changed to the user specified in the setting .BR chown_username . This is useful from an administrative, and perhaps security, standpoint. Default: NO .TP .B chroot_list_enable If activated, you may provide a list of local users who are placed in a chroot() jail in their home directory upon login. The meaning is slightly different if chroot_local_user is set to YES. In this case, the list becomes a list of users which are NOT to be placed in a chroot() jail. By default, the file containing this list is /etc/vsftpd.chroot_list, but you may override this with the .BR chroot_list_file setting. Default: NO .TP .B chroot_local_user If set to YES, local users will be (by default) placed in a chroot() jail in their home directory after login. .BR Warning: This option has security implications, especially if the users have upload permission, or shell access. Only enable if you know what you are doing. Note that these security implications are not vsftpd specific. They apply to all FTP daemons which offer to put local users in chroot() jails. Default: NO .TP .B connect_from_port_20 This controls whether PORT style data connections use port 20 (ftp-data) on the server machine. For security reasons, some clients may insist that this is the case. Conversely, disabling this option enables vsftpd to run with slightly less privilege. Default: NO (but the sample config file enables it) .TP .B debug_ssl If true, OpenSSL connection diagnostics are dumped to the vsftpd log file. (Added in v2.0.6). Default: NO .TP .B delete_failed_uploads If true, any failed upload files are deleted. (Added in v2.0.7). Default: NO .TP .B deny_email_enable If activated, you may provide a list of anonymous password e-mail responses which cause login to be denied. By default, the file containing this list is /etc/vsftpd.banned_emails, but you may override this with the .BR banned_email_file setting. Default: NO .TP .B dirlist_enable If set to NO, all directory list commands will give permission denied. Default: YES .TP .B dirmessage_enable If enabled, users of the FTP server can be shown messages when they first enter a new directory. By default, a directory is scanned for the file .message, but that may be overridden with the configuration setting .BR message_file . Default: NO (but the sample config file enables it) .TP .B download_enable If set to NO, all download requests will give permission denied. Default: YES .TP .B dual_log_enable If enabled, two log files are generated in parallel, going by default to .BR /var/log/xferlog and .BR /var/log/vsftpd.log . The former is a wu-ftpd style transfer log, parseable by standard tools. The latter is vsftpd's own style log. Default: NO .TP .B force_dot_files If activated, files and directories starting with . will be shown in directory listings even if the "a" flag was not used by the client. This override excludes the "." and ".." entries. Default: NO .TP .B force_anon_data_ssl Only applies if .BR ssl_enable is activated. If activated, all anonymous logins are forced to use a secure SSL connection in order to send and receive data on data connections. Default: NO .TP .B force_anon_logins_ssl Only applies if .BR ssl_enable is activated. If activated, all anonymous logins are forced to use a secure SSL connection in order to send the password. Default: NO .TP .B force_local_data_ssl Only applies if .BR ssl_enable is activated. If activated, all non-anonymous logins are forced to use a secure SSL connection in order to send and receive data on data connections. Default: YES .TP .B force_local_logins_ssl Only applies if .BR ssl_enable is activated. If activated, all non-anonymous logins are forced to use a secure SSL connection in order to send the password. Default: YES .TP .B guest_enable If enabled, all non-anonymous logins are classed as "guest" logins. A guest login is remapped to the user specified in the .BR guest_username setting. Default: NO .TP .B hide_ids If enabled, all user and group information in directory listings will be displayed as "ftp". Default: NO .TP .B implicit_ssl If enabled, an SSL handshake is the first thing expect on all connections (the FTPS protocol). To support explicit SSL and/or plain text too, a separate vsftpd listener process should be run. Default: NO .TP .B listen If enabled, vsftpd will run in standalone mode. This means that vsftpd must not be run from an inetd of some kind. Instead, the vsftpd executable is run once directly. vsftpd itself will then take care of listening for and handling incoming connections. Default: NO .TP .B listen_ipv6 Like the listen parameter, except vsftpd will listen on an IPv6 socket instead of an IPv4 one. This parameter and the listen parameter are mutually exclusive. Default: NO .TP .B local_enable Controls whether local logins are permitted or not. If enabled, normal user accounts in /etc/passwd (or wherever your PAM config references) may be used to log in. This must be enable for any non-anonymous login to work, including virtual users. Default: NO .TP .B lock_upload_files When enabled, all uploads proceed with a write lock on the upload file. All downloads proceed with a shared read lock on the download file. WARNING! Before enabling this, be aware that malicious readers could starve a writer wanting to e.g. append a file. Default: YES .TP .B log_ftp_protocol When enabled, all FTP requests and responses are logged, providing the option xferlog_std_format is not enabled. Useful for debugging. Default: NO .TP .B ls_recurse_enable When enabled, this setting will allow the use of "ls -R". This is a minor security risk, because a ls -R at the top level of a large site may consume a lot of resources. Default: NO .TP .B mdtm_write When enabled, this setting will allow MDTM to set file modification times (subject to the usual access checks). Default: YES .TP .B no_anon_password When enabled, this prevents vsftpd from asking for an anonymous password - the anonymous user will log straight in. Default: NO .TP .B no_log_lock When enabled, this prevents vsftpd from taking a file lock when writing to log files. This option should generally not be enabled. It exists to workaround operating system bugs such as the Solaris / Veritas filesystem combination which has been observed to sometimes exhibit hangs trying to lock log files. Default: NO .TP .B one_process_model If you have a Linux 2.4 kernel, it is possible to use a different security model which only uses one process per connection. It is a less pure security model, but gains you performance. You really don't want to enable this unless you know what you are doing, and your site supports huge numbers of simultaneously connected users. Default: NO .TP .B passwd_chroot_enable If enabled, along with .BR chroot_local_user , then a chroot() jail location may be specified on a per-user basis. Each user's jail is derived from their home directory string in /etc/passwd. The occurrence of /./ in the home directory string denotes that the jail is at that particular location in the path. Default: NO .TP .B pasv_addr_resolve Set to YES if you want to use a hostname (as opposed to IP address) in the .BR pasv_address option. Default: NO .TP .B pasv_enable Set to NO if you want to disallow the PASV method of obtaining a data connection. Default: YES .TP .B pasv_promiscuous Set to YES if you want to disable the PASV security check that ensures the data connection originates from the same IP address as the control connection. Only enable if you know what you are doing! The only legitimate use for this is in some form of secure tunnelling scheme, or perhaps to facilitate FXP support. Default: NO .TP .B port_enable Set to NO if you want to disallow the PORT method of obtaining a data connection. Default: YES .TP .B port_promiscuous Set to YES if you want to disable the PORT security check that ensures that outgoing data connections can only connect to the client. Only enable if you know what you are doing! Default: NO .TP .B require_cert If set to yes, all SSL client connections are required to present a client certificate. The degree of validation applied to this certificate is controlled by .BR validate_cert (Added in v2.0.6). Default: NO .TP .B require_ssl_reuse If set to yes, all SSL data connections are required to exhibit SSL session reuse (which proves that they know the same master secret as the control channel). Although this is a secure default, it may break many FTP clients, so you may want to disable it. For a discussion of the consequences, see http://scarybeastsecurity.blogspot.com/2009/02/vsftpd-210-released.html (Added in v2.1.0). Default: YES .TP .B run_as_launching_user Set to YES if you want vsftpd to run as the user which launched vsftpd. This is useful where root access is not available. MASSIVE WARNING! Do NOT enable this option unless you totally know what you are doing, as naive use of this option can create massive security problems. Specifically, vsftpd does not / cannot use chroot technology to restrict file access when this option is set (even if launched by root). A poor substitute could be to use a .BR deny_file setting such as {/*,*..*}, but the reliability of this cannot compare to chroot, and should not be relied on. If using this option, many restrictions on other options apply. For example, options requiring privilege such as non-anonymous logins, upload ownership changing, connecting from port 20 and listen ports less than 1024 are not expected to work. Other options may be impacted. Default: NO .TP .B secure_email_list_enable Set to YES if you want only a specified list of e-mail passwords for anonymous logins to be accepted. This is useful as a low-hassle way of restricting access to low-security content without needing virtual users. When enabled, anonymous logins are prevented unless the password provided is listed in the file specified by the .BR email_password_file setting. The file format is one password per line, no extra whitespace. The default filename is /etc/vsftpd.email_passwords. Default: NO .TP .B session_support This controls whether vsftpd attempts to maintain sessions for logins. If vsftpd is maintaining sessions, it will try and update utmp and wtmp. It will also open a pam_session if using PAM to authenticate, and only close this upon logout. You may wish to disable this if you do not need session logging, and you wish to give vsftpd more opportunity to run with less processes and / or less privilege. NOTE - utmp and wtmp support is only provided with PAM enabled builds. Default: NO .TP .B setproctitle_enable If enabled, vsftpd will try and show session status information in the system process listing. In other words, the reported name of the process will change to reflect what a vsftpd session is doing (idle, downloading etc). You probably want to leave this off for security purposes. Default: NO .TP .B ssl_enable If enabled, and vsftpd was compiled against OpenSSL, vsftpd will support secure connections via SSL. This applies to the control connection (including login) and also data connections. You'll need a client with SSL support too. NOTE!! Beware enabling this option. Only enable it if you need it. vsftpd can make no guarantees about the security of the OpenSSL libraries. By enabling this option, you are declaring that you trust the security of your installed OpenSSL library. Default: NO .TP .B ssl_request_cert If enabled, vsftpd will request (but not necessarily require; see .BR require_cert) a certificate on incoming SSL connections. Normally this should not cause any trouble at all, but IBM zOS seems to have issues. (New in v2.0.7). Default: YES .TP .B ssl_sslv2 Only applies if .BR ssl_enable is activated. If enabled, this option will permit SSL v2 protocol connections. TLS v1 connections are preferred. Default: NO .TP .B ssl_sslv3 Only applies if .BR ssl_enable is activated. If enabled, this option will permit SSL v3 protocol connections. TLS v1 connections are preferred. Default: NO .TP .B ssl_tlsv1 Only applies if .BR ssl_enable is activated. If enabled, this option will permit TLS v1 protocol connections. TLS v1 connections are preferred. Default: YES .TP .B strict_ssl_read_eof If enabled, SSL data uploads are required to terminate via SSL, not an EOF on the socket. This option is required to be sure that an attacker did not terminate an upload prematurely with a faked TCP FIN. Unfortunately, it is not enabled by default because so few clients get it right. (New in v2.0.7). Default: NO .TP .B strict_ssl_write_shutdown If enabled, SSL data downloads are required to terminate via SSL, not an EOF on the socket. This is off by default as I was unable to find a single FTP client that does this. It is minor. All it affects is our ability to tell whether the client confirmed full receipt of the file. Even without this option, the client is able to check the integrity of the download. (New in v2.0.7). Default: NO .TP .B syslog_enable If enabled, then any log output which would have gone to /var/log/vsftpd.log goes to the system log instead. Logging is done under the FTPD facility. Default: NO .TP .B tcp_wrappers If enabled, and vsftpd was compiled with tcp_wrappers support, incoming connections will be fed through tcp_wrappers access control. Furthermore, there is a mechanism for per-IP based configuration. If tcp_wrappers sets the VSFTPD_LOAD_CONF environment variable, then the vsftpd session will try and load the vsftpd configuration file specified in this variable. Default: NO .TP .B text_userdb_names By default, numeric IDs are shown in the user and group fields of directory listings. You can get textual names by enabling this parameter. It is off by default for performance reasons. Default: NO .TP .B tilde_user_enable If enabled, vsftpd will try and resolve pathnames such as ~chris/pics, i.e. a tilde followed by a username. Note that vsftpd will always resolve the pathnames ~ and ~/something (in this case the ~ resolves to the initial login directory). Note that ~user paths will only resolve if the file .BR /etc/passwd may be found within the _current_ chroot() jail. Default: NO .TP .B use_localtime If enabled, vsftpd will display directory listings with the time in your local time zone. The default is to display GMT. The times returned by the MDTM FTP command are also affected by this option. Default: NO .TP .B use_sendfile An internal setting used for testing the relative benefit of using the sendfile() system call on your platform. Default: YES .TP .B userlist_deny This option is examined if .B userlist_enable is activated. If you set this setting to NO, then users will be denied login unless they are explicitly listed in the file specified by .BR userlist_file . When login is denied, the denial is issued before the user is asked for a password. Default: YES .TP .B userlist_enable If enabled, vsftpd will load a list of usernames, from the filename given by .BR userlist_file . If a user tries to log in using a name in this file, they will be denied before they are asked for a password. This may be useful in preventing cleartext passwords being transmitted. See also .BR userlist_deny . Default: NO .TP .B validate_cert If set to yes, all SSL client certificates received must validate OK. Self-signed certs do not constitute OK validation. (New in v2.0.6). Default: NO .TP .B virtual_use_local_privs If enabled, virtual users will use the same privileges as local users. By default, virtual users will use the same privileges as anonymous users, which tends to be more restrictive (especially in terms of write access). Default: NO .TP .B write_enable This controls whether any FTP commands which change the filesystem are allowed or not. These commands are: STOR, DELE, RNFR, RNTO, MKD, RMD, APPE and SITE. Default: NO .TP .B xferlog_enable If enabled, a log file will be maintained detailling uploads and downloads. By default, this file will be placed at /var/log/vsftpd.log, but this location may be overridden using the configuration setting .BR vsftpd_log_file . Default: NO (but the sample config file enables it) .TP .B xferlog_std_format If enabled, the transfer log file will be written in standard xferlog format, as used by wu-ftpd. This is useful because you can reuse existing transfer statistics generators. The default format is more readable, however. The default location for this style of log file is /var/log/xferlog, but you may change it with the setting .BR xferlog_file . Default: NO .SH NUMERIC OPTIONS Below is a list of numeric options. A numeric option must be set to a non negative integer. Octal numbers are supported, for convenience of the umask options. To specify an octal number, use 0 as the first digit of the number. .TP .B accept_timeout The timeout, in seconds, for a remote client to establish connection with a PASV style data connection. Default: 60 .TP .B anon_max_rate The maximum data transfer rate permitted, in bytes per second, for anonymous clients. Default: 0 (unlimited) .TP .B anon_umask The value that the umask for file creation is set to for anonymous users. NOTE! If you want to specify octal values, remember the "0" prefix otherwise the value will be treated as a base 10 integer! Default: 077 .TP .B chown_upload_mode The file mode to force for chown()ed anonymous uploads. (Added in v2.0.6). Default: 0600 .TP .B connect_timeout The timeout, in seconds, for a remote client to respond to our PORT style data connection. Default: 60 .TP .B data_connection_timeout The timeout, in seconds, which is roughly the maximum time we permit data transfers to stall for with no progress. If the timeout triggers, the remote client is kicked off. Default: 300 .TP .B delay_failed_login The number of seconds to pause prior to reporting a failed login. Default: 1 .TP .B delay_successful_login The number of seconds to pause prior to allowing a successful login. Default: 0 .TP .B file_open_mode The permissions with which uploaded files are created. Umasks are applied on top of this value. You may wish to change to 0777 if you want uploaded files to be executable. Default: 0666 .TP .B ftp_data_port The port from which PORT style connections originate (as long as the poorly named .BR connect_from_port_20 is enabled). Default: 20 .TP .B idle_session_timeout The timeout, in seconds, which is the maximum time a remote client may spend between FTP commands. If the timeout triggers, the remote client is kicked off. Default: 300 .TP .B listen_port If vsftpd is in standalone mode, this is the port it will listen on for incoming FTP connections. Default: 21 .TP .B local_max_rate The maximum data transfer rate permitted, in bytes per second, for local authenticated users. Default: 0 (unlimited) .TP .B local_umask The value that the umask for file creation is set to for local users. NOTE! If you want to specify octal values, remember the "0" prefix otherwise the value will be treated as a base 10 integer! Default: 077 .TP .B max_clients If vsftpd is in standalone mode, this is the maximum number of clients which may be connected. Any additional clients connecting will get an error message. Default: 0 (unlimited) .TP .B max_login_fails After this many login failures, the session is killed. Default: 3 .TP .B max_per_ip If vsftpd is in standalone mode, this is the maximum number of clients which may be connected from the same source internet address. A client will get an error message if they go over this limit. Default: 0 (unlimited) .TP .B pasv_max_port The maximum port to allocate for PASV style data connections. Can be used to specify a narrow port range to assist firewalling. Default: 0 (use any port) .TP .B pasv_min_port The minimum port to allocate for PASV style data connections. Can be used to specify a narrow port range to assist firewalling. Default: 0 (use any port) .TP .B trans_chunk_size You probably don't want to change this, but try setting it to something like 8192 for a much smoother bandwidth limiter. Default: 0 (let vsftpd pick a sensible setting) .SH STRING OPTIONS Below is a list of string options. .TP .B anon_root This option represents a directory which vsftpd will try to change into after an anonymous login. Failure is silently ignored. Default: (none) .TP .B banned_email_file This option is the name of a file containing a list of anonymous e-mail passwords which are not permitted. This file is consulted if the option .BR deny_email_enable is enabled. Default: /etc/vsftpd.banned_emails .TP .B banner_file This option is the name of a file containing text to display when someone connects to the server. If set, it overrides the banner string provided by the .BR ftpd_banner option. Default: (none) .TP .B ca_certs_file This option is the name of a file to load Certificate Authority certs from, for the purpose of validating client certs. The loaded certs are also advertised to the client, to cater for TLSv1.0 clients such as the z/OS FTP client. Regrettably, the default SSL CA cert paths are not used, because of vsftpd's use of restricted filesystem spaces (chroot). (Added in v2.0.6). Default: (none) .TP .B chown_username This is the name of the user who is given ownership of anonymously uploaded files. This option is only relevant if another option, .BR chown_uploads , is set. Default: root .TP .B chroot_list_file The option is the name of a file containing a list of local users which will be placed in a chroot() jail in their home directory. This option is only relevant if the option .BR chroot_list_enable is enabled. If the option .BR chroot_local_user is enabled, then the list file becomes a list of users to NOT place in a chroot() jail. Default: /etc/vsftpd.chroot_list .TP .B cmds_allowed This options specifies a comma separated list of allowed FTP commands (post login. USER, PASS and QUIT and others are always allowed pre-login). Other commands are rejected. This is a powerful method of really locking down an FTP server. Example: cmds_allowed=PASV,RETR,QUIT Default: (none) .TP .B cmds_denied This options specifies a comma separated list of denied FTP commands (post login. USER, PASS, QUIT and others are always allowed pre-login). If a command appears on both this and .BR cmds_allowed then the denial takes precedence. (Added in v2.1.0). Default: (none) .TP .B deny_file This option can be used to set a pattern for filenames (and directory names etc.) which should not be accessible in any way. The affected items are not hidden, but any attempt to do anything to them (download, change into directory, affect something within directory etc.) will be denied. This option is very simple, and should not be used for serious access control - the filesystem's permissions should be used in preference. However, this option may be useful in certain virtual user setups. In particular aware that if a filename is accessible by a variety of names (perhaps due to symbolic links or hard links), then care must be taken to deny access to all the names. Access will be denied to items if their name contains the string given by hide_file, or if they match the regular expression specified by hide_file. Note that vsftpd's regular expression matching code is a simple implementation which is a subset of full regular expression functionality. Because of this, you will need to carefully and exhaustively test any application of this option. And you are recommended to use filesystem permissions for any important security policies due to their greater reliability. Supported regex syntax is any number of *, ? and unnested {,} operators. Regex matching is only supported on the last component of a path, e.g. a/b/? is supported but a/?/c is not. Example: deny_file={*.mp3,*.mov,.private} Default: (none) .TP .B dsa_cert_file This option specifies the location of the DSA certificate to use for SSL encrypted connections. Default: (none - an RSA certificate suffices) .TP .B dsa_private_key_file This option specifies the location of the DSA private key to use for SSL encrypted connections. If this option is not set, the private key is expected to be in the same file as the certificate. Default: (none) .TP .B email_password_file This option can be used to provide an alternate file for usage by the .BR secure_email_list_enable setting. Default: /etc/vsftpd.email_passwords .TP .B ftp_username This is the name of the user we use for handling anonymous FTP. The home directory of this user is the root of the anonymous FTP area. Default: ftp .TP .B ftpd_banner This string option allows you to override the greeting banner displayed by vsftpd when a connection first comes in. Default: (none - default vsftpd banner is displayed) .TP .B guest_username See the boolean setting .BR guest_enable for a description of what constitutes a guest login. This setting is the real username which guest users are mapped to. Default: ftp .TP .B hide_file This option can be used to set a pattern for filenames (and directory names etc.) which should be hidden from directory listings. Despite being hidden, the files / directories etc. are fully accessible to clients who know what names to actually use. Items will be hidden if their names contain the string given by hide_file, or if they match the regular expression specified by hide_file. Note that vsftpd's regular expression matching code is a simple implementation which is a subset of full regular expression functionality. See .BR deny_file for details of exactly what regex syntax is supported. Example: hide_file={*.mp3,.hidden,hide*,h?} Default: (none) .TP .B listen_address If vsftpd is in standalone mode, the default listen address (of all local interfaces) may be overridden by this setting. Provide a numeric IP address. Default: (none) .TP .B listen_address6 Like listen_address, but specifies a default listen address for the IPv6 listener (which is used if listen_ipv6 is set). Format is standard IPv6 address format. Default: (none) .TP .B local_root This option represents a directory which vsftpd will try to change into after a local (i.e. non-anonymous) login. Failure is silently ignored. Default: (none) .TP .B message_file This option is the name of the file we look for when a new directory is entered. The contents are displayed to the remote user. This option is only relevant if the option .BR dirmessage_enable is enabled. Default: .message .TP .B nopriv_user This is the name of the user that is used by vsftpd when it wants to be totally unprivileged. Note that this should be a dedicated user, rather than nobody. The user nobody tends to be used for rather a lot of important things on most machines. Default: nobody .TP .B pam_service_name This string is the name of the PAM service vsftpd will use. Default: ftp .TP .B pasv_address Use this option to override the IP address that vsftpd will advertise in response to the PASV command. Provide a numeric IP address, unless .BR pasv_addr_resolve is enabled, in which case you can provide a hostname which will be DNS resolved for you at startup. Default: (none - the address is taken from the incoming connected socket) .TP .B rsa_cert_file This option specifies the location of the RSA certificate to use for SSL encrypted connections. Default: /usr/share/ssl/certs/vsftpd.pem .TP .B rsa_private_key_file This option specifies the location of the RSA private key to use for SSL encrypted connections. If this option is not set, the private key is expected to be in the same file as the certificate. Default: (none) .TP .B secure_chroot_dir This option should be the name of a directory which is empty. Also, the directory should not be writable by the ftp user. This directory is used as a secure chroot() jail at times vsftpd does not require filesystem access. Default: /usr/share/empty .TP .B ssl_ciphers This option can be used to select which SSL ciphers vsftpd will allow for encrypted SSL connections. See the .BR ciphers man page for further details. Note that restricting ciphers can be a useful security precaution as it prevents malicious remote parties forcing a cipher which they have found problems with. Default: DES-CBC3-SHA .TP .B user_config_dir This powerful option allows the override of any config option specified in the manual page, on a per-user basis. Usage is simple, and is best illustrated with an example. If you set .BR user_config_dir to be .BR /etc/vsftpd_user_conf and then log on as the user "chris", then vsftpd will apply the settings in the file .BR /etc/vsftpd_user_conf/chris for the duration of the session. The format of this file is as detailed in this manual page! PLEASE NOTE that not all settings are effective on a per-user basis. For example, many settings only prior to the user's session being started. Examples of settings which will not affect any behviour on a per-user basis include listen_address, banner_file, max_per_ip, max_clients, xferlog_file, etc. Default: (none) .TP .B user_sub_token This option is useful is conjunction with virtual users. It is used to automatically generate a home directory for each virtual user, based on a template. For example, if the home directory of the real user specified via .BR guest_username is .BR /home/virtual/$USER , and .BR user_sub_token is set to .BR $USER , then when virtual user fred logs in, he will end up (usually chroot()'ed) in the directory .BR /home/virtual/fred . This option also takes affect if .BR local_root contains .BR user_sub_token . Default: (none) .TP .B userlist_file This option is the name of the file loaded when the .BR userlist_enable option is active. Default: /etc/vsftpd.user_list .TP .B vsftpd_log_file This option is the name of the file to which we write the vsftpd style log file. This log is only written if the option .BR xferlog_enable is set, and .BR xferlog_std_format is NOT set. Alternatively, it is written if you have set the option .BR dual_log_enable . One further complication - if you have set .BR syslog_enable , then this file is not written and output is sent to the system log instead. Default: /var/log/vsftpd.log .TP .B xferlog_file This option is the name of the file to which we write the wu-ftpd style transfer log. The transfer log is only written if the option .BR xferlog_enable is set, along with .BR xferlog_std_format . Alternatively, it is written if you have set the option .BR dual_log_enable . Default: /var/log/xferlog .SH AUTHOR scarybeasts@gmail.com vsftpd-3.0.3/standalone.h0000644000175000017500000000113610750743700014332 0ustar chrischris#ifndef VSF_STANDALONE_H #define VSF_STANDALONE_H struct vsf_client_launch { unsigned int num_children; unsigned int num_this_ip; }; /* vsf_standalone_main() * PURPOSE * This function starts listening on the network for incoming FTP connections. * When it gets one, it returns to the caller in a new process, with file * descriptor 0, 1 and 2 set to the network socket of the new client. * * RETURNS * Returns a structure representing the current number of clients, and * instances for this IP addresss. */ struct vsf_client_launch vsf_standalone_main(void); #endif /* VSF_STANDALONE_H */ vsftpd-3.0.3/RedHat/0000755000175000017500000000000011122223646013173 5ustar chrischrisvsftpd-3.0.3/RedHat/vsftpd.log0000644000175000017500000000013711122223643015202 0ustar chrischris/var/log/vsftpd.log { # ftpd doesn't handle SIGHUP properly nocompress missingok } vsftpd-3.0.3/RedHat/vsftpd.pam0000644000175000017500000000047210750743700015207 0ustar chrischris#%PAM-1.0 auth required /lib/security/pam_listfile.so item=user sense=deny file=/etc/ftpusers onerr=succeed auth required /lib/security/pam_unix.so shadow nullok auth required /lib/security/pam_shells.so account required /lib/security/pam_unix.so session required /lib/security/pam_unix.so vsftpd-3.0.3/RedHat/README.spec0000644000175000017500000000017510750743700015013 0ustar chrischrisThe .spec files have gone because they are old and buggy. All modern versions of RedHat now include vsftpd packages anyway. vsftpd-3.0.3/tunables.c0000644000175000017500000002323112554064723014017 0ustar chrischris/* * Part of Very Secure FTPd * Licence: GPL v2 * Author: Chris Evans * tunables.c */ #include "tunables.h" #include "sysutil.h" int tunable_anonymous_enable; int tunable_local_enable; int tunable_pasv_enable; int tunable_port_enable; int tunable_chroot_local_user; int tunable_write_enable; int tunable_anon_upload_enable; int tunable_anon_mkdir_write_enable; int tunable_anon_other_write_enable; int tunable_chown_uploads; int tunable_connect_from_port_20; int tunable_xferlog_enable; int tunable_dirmessage_enable; int tunable_anon_world_readable_only; int tunable_async_abor_enable; int tunable_ascii_upload_enable; int tunable_ascii_download_enable; int tunable_one_process_model; int tunable_xferlog_std_format; int tunable_pasv_promiscuous; int tunable_deny_email_enable; int tunable_chroot_list_enable; int tunable_setproctitle_enable; int tunable_text_userdb_names; int tunable_ls_recurse_enable; int tunable_log_ftp_protocol; int tunable_guest_enable; int tunable_userlist_enable; int tunable_userlist_deny; int tunable_use_localtime; int tunable_check_shell; int tunable_hide_ids; int tunable_listen; int tunable_port_promiscuous; int tunable_passwd_chroot_enable; int tunable_no_anon_password; int tunable_tcp_wrappers; int tunable_use_sendfile; int tunable_force_dot_files; int tunable_listen_ipv6; int tunable_dual_log_enable; int tunable_syslog_enable; int tunable_background; int tunable_virtual_use_local_privs; int tunable_session_support; int tunable_download_enable; int tunable_dirlist_enable; int tunable_chmod_enable; int tunable_secure_email_list_enable; int tunable_run_as_launching_user; int tunable_no_log_lock; int tunable_ssl_enable; int tunable_allow_anon_ssl; int tunable_force_local_logins_ssl; int tunable_force_local_data_ssl; int tunable_sslv2; int tunable_sslv3; int tunable_tlsv1; int tunable_tilde_user_enable; int tunable_force_anon_logins_ssl; int tunable_force_anon_data_ssl; int tunable_mdtm_write; int tunable_lock_upload_files; int tunable_pasv_addr_resolve; int tunable_debug_ssl; int tunable_require_cert; int tunable_validate_cert; int tunable_strict_ssl_read_eof; int tunable_strict_ssl_write_shutdown; int tunable_ssl_request_cert; int tunable_delete_failed_uploads; int tunable_implicit_ssl; int tunable_ptrace_sandbox; int tunable_require_ssl_reuse; int tunable_isolate; int tunable_isolate_network; int tunable_ftp_enable; int tunable_http_enable; int tunable_seccomp_sandbox; int tunable_allow_writeable_chroot; unsigned int tunable_accept_timeout; unsigned int tunable_connect_timeout; unsigned int tunable_local_umask; unsigned int tunable_anon_umask; unsigned int tunable_ftp_data_port; unsigned int tunable_idle_session_timeout; unsigned int tunable_data_connection_timeout; unsigned int tunable_pasv_min_port; unsigned int tunable_pasv_max_port; unsigned int tunable_anon_max_rate; unsigned int tunable_local_max_rate; unsigned int tunable_listen_port; unsigned int tunable_max_clients; unsigned int tunable_file_open_mode; unsigned int tunable_max_per_ip; unsigned int tunable_trans_chunk_size; unsigned int tunable_delay_failed_login; unsigned int tunable_delay_successful_login; unsigned int tunable_max_login_fails; unsigned int tunable_chown_upload_mode; const char* tunable_secure_chroot_dir; const char* tunable_ftp_username; const char* tunable_chown_username; const char* tunable_xferlog_file; const char* tunable_vsftpd_log_file; const char* tunable_message_file; const char* tunable_nopriv_user; const char* tunable_ftpd_banner; const char* tunable_banned_email_file; const char* tunable_chroot_list_file; const char* tunable_pam_service_name; const char* tunable_guest_username; const char* tunable_userlist_file; const char* tunable_anon_root; const char* tunable_local_root; const char* tunable_banner_file; const char* tunable_pasv_address; const char* tunable_listen_address; const char* tunable_user_config_dir; const char* tunable_listen_address6; const char* tunable_cmds_allowed; const char* tunable_cmds_denied; const char* tunable_hide_file; const char* tunable_deny_file; const char* tunable_user_sub_token; const char* tunable_email_password_file; const char* tunable_rsa_cert_file; const char* tunable_dsa_cert_file; const char* tunable_ssl_ciphers; const char* tunable_rsa_private_key_file; const char* tunable_dsa_private_key_file; const char* tunable_ca_certs_file; static void install_str_setting(const char* p_value, const char** p_storage); void tunables_load_defaults() { tunable_anonymous_enable = 1; tunable_local_enable = 0; tunable_pasv_enable = 1; tunable_port_enable = 1; tunable_chroot_local_user = 0; tunable_write_enable = 0; tunable_anon_upload_enable = 0; tunable_anon_mkdir_write_enable = 0; tunable_anon_other_write_enable = 0; tunable_chown_uploads = 0; tunable_connect_from_port_20 = 0; tunable_xferlog_enable = 0; tunable_dirmessage_enable = 0; tunable_anon_world_readable_only = 1; tunable_async_abor_enable = 0; tunable_ascii_upload_enable = 0; tunable_ascii_download_enable = 0; tunable_one_process_model = 0; tunable_xferlog_std_format = 0; tunable_pasv_promiscuous = 0; tunable_deny_email_enable = 0; tunable_chroot_list_enable = 0; tunable_setproctitle_enable = 0; tunable_text_userdb_names = 0; tunable_ls_recurse_enable = 0; tunable_log_ftp_protocol = 0; tunable_guest_enable = 0; tunable_userlist_enable = 0; tunable_userlist_deny = 1; tunable_use_localtime = 0; tunable_check_shell = 1; tunable_hide_ids = 0; tunable_listen = 1; tunable_port_promiscuous = 0; tunable_passwd_chroot_enable = 0; tunable_no_anon_password = 0; tunable_tcp_wrappers = 0; tunable_use_sendfile = 1; tunable_force_dot_files = 0; tunable_listen_ipv6 = 0; tunable_dual_log_enable = 0; tunable_syslog_enable = 0; tunable_background = 0; tunable_virtual_use_local_privs = 0; tunable_session_support = 0; tunable_download_enable = 1; tunable_dirlist_enable = 1; tunable_chmod_enable = 1; tunable_secure_email_list_enable = 0; tunable_run_as_launching_user = 0; tunable_no_log_lock = 0; tunable_ssl_enable = 0; tunable_allow_anon_ssl = 0; tunable_force_local_logins_ssl = 1; tunable_force_local_data_ssl = 1; tunable_sslv2 = 0; tunable_sslv3 = 0; tunable_tlsv1 = 1; tunable_tilde_user_enable = 0; tunable_force_anon_logins_ssl = 0; tunable_force_anon_data_ssl = 0; tunable_mdtm_write = 1; tunable_lock_upload_files = 1; tunable_pasv_addr_resolve = 0; tunable_debug_ssl = 0; tunable_require_cert = 0; tunable_validate_cert = 0; tunable_strict_ssl_read_eof = 1; tunable_strict_ssl_write_shutdown = 0; tunable_ssl_request_cert = 1; tunable_delete_failed_uploads = 0; tunable_implicit_ssl = 0; tunable_ptrace_sandbox = 0; tunable_require_ssl_reuse = 1; tunable_isolate = 1; tunable_isolate_network = 1; tunable_ftp_enable = 1; tunable_http_enable = 0; tunable_seccomp_sandbox = 1; tunable_allow_writeable_chroot = 0; tunable_accept_timeout = 60; tunable_connect_timeout = 60; tunable_local_umask = 077; tunable_anon_umask = 077; tunable_ftp_data_port = 20; tunable_idle_session_timeout = 300; tunable_data_connection_timeout = 300; /* IPPORT_USERRESERVED + 1 */ tunable_pasv_min_port = 5001; tunable_pasv_max_port = 0; tunable_anon_max_rate = 0; tunable_local_max_rate = 0; /* IPPORT_FTP */ tunable_listen_port = 21; tunable_max_clients = 2000; /* -rw-rw-rw- */ tunable_file_open_mode = 0666; tunable_max_per_ip = 50; tunable_trans_chunk_size = 0; tunable_delay_failed_login = 1; tunable_delay_successful_login = 0; tunable_max_login_fails = 3; /* -rw------- */ tunable_chown_upload_mode = 0600; install_str_setting("/usr/share/empty", &tunable_secure_chroot_dir); install_str_setting("ftp", &tunable_ftp_username); install_str_setting("root", &tunable_chown_username); install_str_setting("/var/log/xferlog", &tunable_xferlog_file); install_str_setting("/var/log/vsftpd.log", &tunable_vsftpd_log_file); install_str_setting(".message", &tunable_message_file); install_str_setting("nobody", &tunable_nopriv_user); install_str_setting(0, &tunable_ftpd_banner); install_str_setting("/etc/vsftpd.banned_emails", &tunable_banned_email_file); install_str_setting("/etc/vsftpd.chroot_list", &tunable_chroot_list_file); install_str_setting("ftp", &tunable_pam_service_name); install_str_setting("ftp", &tunable_guest_username); install_str_setting("/etc/vsftpd.user_list", &tunable_userlist_file); install_str_setting(0, &tunable_anon_root); install_str_setting(0, &tunable_local_root); install_str_setting(0, &tunable_banner_file); install_str_setting(0, &tunable_pasv_address); install_str_setting(0, &tunable_listen_address); install_str_setting(0, &tunable_user_config_dir); install_str_setting(0, &tunable_listen_address6); install_str_setting(0, &tunable_cmds_allowed); install_str_setting(0, &tunable_cmds_denied); install_str_setting(0, &tunable_hide_file); install_str_setting(0, &tunable_deny_file); install_str_setting(0, &tunable_user_sub_token); install_str_setting("/etc/vsftpd.email_passwords", &tunable_email_password_file); install_str_setting("/usr/share/ssl/certs/vsftpd.pem", &tunable_rsa_cert_file); install_str_setting(0, &tunable_dsa_cert_file); install_str_setting("ECDHE-RSA-AES256-GCM-SHA384", &tunable_ssl_ciphers); install_str_setting(0, &tunable_rsa_private_key_file); install_str_setting(0, &tunable_dsa_private_key_file); install_str_setting(0, &tunable_ca_certs_file); } void install_str_setting(const char* p_value, const char** p_storage) { char* p_curr_val = (char*) *p_storage; if (p_curr_val != 0) { vsf_sysutil_free(p_curr_val); } if (p_value != 0) { p_value = vsf_sysutil_strdup(p_value); } *p_storage = p_value; } vsftpd-3.0.3/AUDIT0000644000175000017500000000256010750743677012641 0ustar chrischrisThis file contains information on the audit status of the code in this program. Each file has an audit code of between 1 and 5, ranging from 1 being unaudited and 5 being heavily audited by multiple competent people. The important rule is that when a file is changed, the audit status goes back to 1, _unless_ the change(s) are audited very carefully as they go in. access.c 2 ascii.c 3 banner.c 2 dirchange.c 3 filestr.c 3 ftpcmdio.c 3 ftpdataio.c 2 hash.c 1 ipaddrparse.c 1 logging.c 3 ls.c 2 main.c 3 netstr.c 3 oneprocess.c 3 parseconf.c 2 postlogin.c 2 postprivparent.c 3 prelogin.c 3 privops.c 2 privparent.c 3 privsock.c 3 secbuf.c 3 secutil.c 3 ssl.c 1 standalone.c 1 str.c 2 strlist.c 2 sysdeputil.c 2 sysstr.c 3 sysutil.c 2 tunables.c 3 twoprocess.c 2 utility.c 3 vsftpd-3.0.3/REFS0000644000175000017500000000326110750743700012514 0ustar chrischrisThe following FTP related references are useful: (Implemented or partially implemented in vsftpd): ================================================= RFC-959, original FTP spec. http://www.rfc-editor.org/rfc/rfc959.txt RFC-1123, the small FTP related section extends / clarifies RFC-959. http://www.rfc-editor.org/rfc/rfc1123.txt RFC-2228, FTP security extensions. vsftpd implements the small subset needed to support TLS / SSL connections. http://www.rfc-editor.org/rfc/rfc2228.txt RFC-2389. Proposes FEAT and OPTS commands. http://www.rfc-editor.org/rfc/rfc2389.txt RFC-2428. Essentially IPv6 support. http://www.rfc-editor.org/rfc/rfc2428.txt "Securing FTP with TLS" (draft-murray-auth-ftp-ssl-09.txt). Document that dives into the standardized behaviour of SSL / TLS connections in conjunction with RFC-2228. http://www.isaserver.org/articles/Securing_FTP_with_TLS.html "Extensions to FTP" (draft-ietf-ftpext-mlst-16.txt). Standardizes SIZE, MDTM, MLST and MLSD. Note that vsftpd has not implemented MLST and MLSD due to lack of demand from users. Perhaps the client support just isn't there. http://www.ietf.org/internet-drafts/draft-ietf-ftpext-mlst-16.txt (Not implemented in vsftpd): ============================ RFC-1579. Proposes an APSV command. No users have requested this in vsftpd; perhaps the client support just isn't there. http://www.rfc-editor.org/rfc/rfc1579.txt RFC-1639. Proposes commands LPRT and LPSV. Seems to be deprecated in favour of EPRT and EPSV in RFC-2428. http://www.rfc-editor.org/rfc/rfc1639.txt RFC-2640. Deals with internationalization and the LANG command. I'm not seeing any vsftpd users with requirements in this area. http://www.rfc-editor.org/rfc/rfc2640.txt vsftpd-3.0.3/BENCHMARKS0000644000175000017500000000553410750743702013401 0ustar chrischris- See also SPEED Update 2nd Nov 2001 ftp.redhat.com ran vsftpd for the RedHat 7.2 release. vsftpd achieved 4,000 concurrent users on a single machine with 1Gb RAM. Even with this insane user count, bandwidth remained totally saturated. The user count could have been higher, but the machine ran out of processes. -- Below are some quick benchmark figures vs. wu-ftpd. This is an untuned BETA version of vsftpd (0.0.10) The executive summary is that wu-ftpd got a thorough thrashing. The most telling statistic is wu-ftpd typically failing to sustain 400 users, whereas vsftpd copes with 1000 with room to spare. A 2.2.x kernel was used. A 2.4.x kernel should make vsftpd look even better relative to wu-ftpd thanks to the sendfile() boosts in 2.4.x. A 2.4.x kernel with zerocopy should be amazing. Many thanks to Andrew Anderson -- Here's some benchmarks that I did on vsftpd vs. wu-ftpd. The tests were run with "dkftpbench -hftpserver -n500 -t600 -f/pub/dkftp/". The attached file are the summary output with time to reach the steady-state condition. The interesting things I noticed are: - In the raw test results, vsftpd had a much higher peak on the x10k.dat transfer run than wu-ftpd did. Wu-ftpd peaked at ~150 connections and bled down to ~130 connections, while vsftpd peaked at ~400 connections and bled down to ~160 connections. I tend to believe the peaks more than the final steady-state that dkftpbench reports, though. - For the other tests, our wu-ftpd setup was limited to 400 connections, but in about half of the x100k/x1000k runs could not even sustain 400 connections, while vsftpd handled 500 easily on those runs. - During the peak runs at x10k, the machine load with vsftpd looked like this (I don't have this data still for the wu-ftpd runs): 01:01:00 AM all 4.92 0.00 21.23 73.85 03:31:00 AM all 4.89 0.00 19.53 75.58 05:11:00 AM all 4.19 0.00 16.89 78.92 07:01:00 AM all 5.61 0.00 22.47 71.92 The steady-state loads were more in the 3-5% user, 10-15% system. For the x100/x1000 loads with vsftpd, the system load looked like this: x100k.dat: 09:01:00 AM all 2.27 0.00 9.79 87.94 x1000k.dat: 11:01:00 AM all 0.42 0.00 5.75 93.83 Not bad -- 500 concurrent users for ~7% system load. - Just for kicks I ran the x1000k test with 1000 users. At peak load: X1000k.dat with 1000 users: 04:41:00 PM all 1.23 0.00 46.59 52.18 Based on what I'm seeing, it looks like if a server had enough bandwidth, it could indeed sustain ~2000 users with the current 2 process model that's implemented in vsftpd. I did notice that dkftpbench slowed down the connection rate after 800 connections. I'm not sure if that was a dkftpbench issue, or if I ran into something other limit. vsftpd-3.0.3/seccompsandbox.c0000644000175000017500000004004012553771527015215 0ustar chrischris/* * Part of Very Secure FTPd * Licence: GPL v2 * Author: Chris Evans * seccompsandbox.c * * Code to lock down the accessible kernel API in a Linux seccomp filter * sandbox. Works in Ubuntu 11.10 and newer. */ #include "seccompsandbox.h" #if defined(__linux__) && defined(__x86_64__) #include "session.h" #include "sysutil.h" #include "tunables.h" #include "utility.h" #include #include #include #include #include #include #include #include #include #include /* #define DEBUG_SIGSYS 1 */ #ifndef PR_SET_SECCOMP #define PR_SET_SECCOMP 22 #endif #ifndef PR_SET_NO_NEW_PRIVS #define PR_SET_NO_NEW_PRIVS 38 #endif #ifndef __NR_openat #define __NR_openat 257 #endif #ifndef O_LARGEFILE #define O_LARGEFILE 00100000 #endif #ifndef O_DIRECTORY #define O_DIRECTORY 00200000 #endif #ifndef O_CLOEXEC #define O_CLOEXEC 002000000 #endif #define kMaxSyscalls 100 #ifdef DEBUG_SIGSYS #include #include void handle_sigsys(int sig) { (void) sig; } #endif static const int kOpenFlags = O_CREAT|O_EXCL|O_APPEND|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_LARGEFILE; static size_t s_syscall_index; static size_t s_1_arg_validations; static size_t s_2_arg_validations; static size_t s_3_arg_validations; static int s_syscalls[kMaxSyscalls]; static int s_errnos[kMaxSyscalls]; static int s_args_1[kMaxSyscalls]; static int s_vals_1[kMaxSyscalls]; static int s_args_2[kMaxSyscalls]; static int s_vals_2[kMaxSyscalls]; static int s_args_3[kMaxSyscalls]; static int s_vals_3[kMaxSyscalls]; static void allow_nr(int nr) { if (s_syscall_index >= kMaxSyscalls) { bug("out of syscall space"); } if (nr < 0) { bug("negative syscall"); } s_errnos[s_syscall_index] = 0; s_syscalls[s_syscall_index++] = nr; } static void reject_nr(int nr, int errcode) { if (s_syscall_index >= kMaxSyscalls) { bug("out of syscall space"); } if (nr < 0) { bug("negative syscall"); } if (errcode < 0 || errcode > 255) { bug("bad errcode"); } s_errnos[s_syscall_index] = errcode; s_syscalls[s_syscall_index++] = nr; } static void allow_nr_1_arg_match(int nr, int arg, int val) { if (s_syscall_index >= kMaxSyscalls) { bug("out of syscall space"); } if (nr < 0) { bug("negative syscall"); } if (arg < 1 || arg > 6) { bug("arg out of range"); } s_args_1[s_syscall_index] = arg; s_vals_1[s_syscall_index] = val; s_errnos[s_syscall_index] = 0; s_syscalls[s_syscall_index++] = nr; s_1_arg_validations++; } static void allow_nr_1_arg_mask(int nr, int arg, int val) { if (s_syscall_index >= kMaxSyscalls) { bug("out of syscall space"); } if (nr < 0) { bug("negative syscall"); } if (arg < 1 || arg > 6) { bug("arg out of range"); } s_args_1[s_syscall_index] = 100 + arg; s_vals_1[s_syscall_index] = val; s_errnos[s_syscall_index] = 0; s_syscalls[s_syscall_index++] = nr; s_1_arg_validations++; } static void allow_nr_2_arg_match(int nr, int arg1, int val1, int arg2, int val2) { if (s_syscall_index >= kMaxSyscalls) { bug("out of syscall space"); } if (nr < 0) { bug("negative syscall"); } if (arg1 < 1 || arg1 > 6) { bug("arg1 out of range"); } if (arg2 < 1 || arg2 > 6) { bug("arg2 out of range"); } s_args_1[s_syscall_index] = arg1; s_vals_1[s_syscall_index] = val1; s_args_2[s_syscall_index] = arg2; s_vals_2[s_syscall_index] = val2; s_errnos[s_syscall_index] = 0; s_syscalls[s_syscall_index++] = nr; s_2_arg_validations++; } static void allow_nr_2_arg_mask_match(int nr, int arg1, int val1, int arg2, int val2) { if (s_syscall_index >= kMaxSyscalls) { bug("out of syscall space"); } if (nr < 0) { bug("negative syscall"); } if (arg1 < 1 || arg1 > 6) { bug("arg1 out of range"); } if (arg2 < 1 || arg2 > 6) { bug("arg2 out of range"); } s_args_1[s_syscall_index] = 100 + arg1; s_vals_1[s_syscall_index] = val1; s_args_2[s_syscall_index] = arg2; s_vals_2[s_syscall_index] = val2; s_errnos[s_syscall_index] = 0; s_syscalls[s_syscall_index++] = nr; s_2_arg_validations++; } static void allow_nr_3_arg_match(int nr, int arg1, int val1, int arg2, int val2, int arg3, int val3) { if (s_syscall_index >= kMaxSyscalls) { bug("out of syscall space"); } if (nr < 0) { bug("negative syscall"); } if (arg1 < 1 || arg1 > 6) { bug("arg1 out of range"); } if (arg2 < 1 || arg2 > 6) { bug("arg2 out of range"); } if (arg3 < 1 || arg3 > 6) { bug("arg3 out of range"); } s_args_1[s_syscall_index] = arg1; s_vals_1[s_syscall_index] = val1; s_args_2[s_syscall_index] = arg2; s_vals_2[s_syscall_index] = val2; s_args_3[s_syscall_index] = arg3; s_vals_3[s_syscall_index] = val3; s_errnos[s_syscall_index] = 0; s_syscalls[s_syscall_index++] = nr; s_3_arg_validations++; } static void seccomp_sandbox_setup_data_connections() { allow_nr_3_arg_match(__NR_socket, 1, PF_INET, 2, SOCK_STREAM, 3, IPPROTO_TCP); allow_nr_3_arg_match(__NR_socket, 1, PF_INET6, 2, SOCK_STREAM, 3, IPPROTO_TCP); allow_nr(__NR_bind); allow_nr(__NR_select); if (tunable_port_enable) { allow_nr(__NR_connect); allow_nr_2_arg_match(__NR_getsockopt, 2, SOL_SOCKET, 3, SO_ERROR); allow_nr_2_arg_match(__NR_setsockopt, 2, SOL_SOCKET, 3, SO_REUSEADDR); allow_nr_1_arg_match(__NR_fcntl, 2, F_GETFL); allow_nr_2_arg_match(__NR_fcntl, 2, F_SETFL, 3, O_RDWR|O_NONBLOCK); allow_nr_2_arg_match(__NR_fcntl, 2, F_SETFL, 3, O_RDWR); } if (tunable_pasv_enable) { allow_nr(__NR_listen); allow_nr(__NR_accept); } } static void seccomp_sandbox_setup_base() { /* Simple reads and writes on existing descriptors. */ allow_nr(__NR_read); allow_nr(__NR_write); /* Needed for memory management. */ allow_nr_2_arg_match(__NR_mmap, 3, PROT_READ|PROT_WRITE, 4, MAP_PRIVATE|MAP_ANON); allow_nr_1_arg_mask(__NR_mprotect, 3, PROT_READ); allow_nr(__NR_munmap); allow_nr(__NR_brk); /* glibc falls back gracefully if mremap() fails during realloc(). */ reject_nr(__NR_mremap, ENOSYS); /* Misc simple low-risk calls. */ allow_nr(__NR_gettimeofday); /* Used by logging. */ allow_nr(__NR_rt_sigreturn); /* Used to handle SIGPIPE. */ allow_nr(__NR_restart_syscall); allow_nr(__NR_close); /* Always need to be able to exit ! */ allow_nr(__NR_exit_group); } void seccomp_sandbox_init() { if (s_syscall_index != 0) { bug("bad state in seccomp_sandbox_init"); } } void seccomp_sandbox_setup_prelogin(const struct vsf_session* p_sess) { (void) p_sess; seccomp_sandbox_setup_base(); /* Peeking FTP commands from the network. */ allow_nr_1_arg_match(__NR_recvfrom, 4, MSG_PEEK); /* Misc simple low-risk calls */ allow_nr(__NR_nanosleep); /* Used for bandwidth / login throttling. */ allow_nr(__NR_getpid); /* Used by logging. */ allow_nr(__NR_shutdown); /* Used for QUIT or a timeout. */ allow_nr_1_arg_match(__NR_fcntl, 2, F_GETFL); /* It's safe to allow O_RDWR in fcntl because these flags cannot be changed. * Also, sockets are O_RDWR. */ allow_nr_2_arg_mask_match(__NR_fcntl, 3, kOpenFlags|O_ACCMODE, 2, F_SETFL); /* Config-dependent items follow. */ if (tunable_idle_session_timeout > 0) { allow_nr(__NR_rt_sigaction); allow_nr(__NR_alarm); } if (tunable_xferlog_enable || tunable_dual_log_enable) { /* For file locking. */ allow_nr_1_arg_match(__NR_fcntl, 2, F_SETLKW); allow_nr_1_arg_match(__NR_fcntl, 2, F_SETLK); } if (tunable_ssl_enable) { allow_nr_1_arg_match(__NR_recvmsg, 3, 0); allow_nr_2_arg_match(__NR_setsockopt, 2, IPPROTO_TCP, 3, TCP_NODELAY); } if (tunable_syslog_enable) { reject_nr(__NR_socket, EACCES); } } void seccomp_sandbox_setup_postlogin(const struct vsf_session* p_sess) { int is_anon = p_sess->is_anonymous; int open_flag = kOpenFlags; if (tunable_write_enable) { open_flag |= O_ACCMODE; } /* Put lstat() first because it is a very hot syscall for large directory * listings. And the current BPF only allows a linear scan of allowed * syscalls. */ allow_nr(__NR_lstat); /* Allow all the simple pre-login things and then expand upon them. */ seccomp_sandbox_setup_prelogin(p_sess); /* Simple file descriptor-based operations. */ if (tunable_xferlog_enable || tunable_dual_log_enable || tunable_lock_upload_files) { allow_nr_1_arg_match(__NR_fcntl, 2, F_SETLKW); allow_nr_1_arg_match(__NR_fcntl, 2, F_SETLK); } if (tunable_async_abor_enable) { allow_nr_2_arg_match(__NR_fcntl, 2, F_SETOWN, 3, vsf_sysutil_getpid()); } allow_nr_2_arg_match(__NR_setsockopt, 2, SOL_SOCKET, 3, SO_KEEPALIVE); allow_nr_2_arg_match(__NR_setsockopt, 2, SOL_SOCKET, 3, SO_LINGER); allow_nr_2_arg_match(__NR_setsockopt, 2, IPPROTO_IP, 3, IP_TOS); allow_nr(__NR_fstat); allow_nr(__NR_lseek); /* Since we use chroot() to restrict filesystem access, we can just blanket * allow open(). */ allow_nr_1_arg_mask(__NR_open, 2, open_flag); allow_nr_1_arg_mask(__NR_openat, 3, open_flag); /* Other pathname-based metadata queries. */ allow_nr(__NR_stat); allow_nr(__NR_readlink); /* Directory handling: query, change, read. */ allow_nr(__NR_getcwd); allow_nr(__NR_chdir); allow_nr(__NR_getdents); /* Misc */ allow_nr(__NR_umask); /* Config-dependent items follow. */ if (tunable_use_sendfile) { allow_nr(__NR_sendfile); } if (tunable_idle_session_timeout > 0 || tunable_data_connection_timeout > 0 || tunable_async_abor_enable) { allow_nr(__NR_rt_sigaction); } if (tunable_idle_session_timeout > 0 || tunable_data_connection_timeout > 0) { allow_nr(__NR_alarm); } if (tunable_one_process_model) { seccomp_sandbox_setup_data_connections(); if (is_anon && tunable_chown_uploads) { allow_nr(__NR_fchmod); allow_nr(__NR_fchown); } } else { /* Need to receieve file descriptors from privileged broker. */ allow_nr_1_arg_match(__NR_recvmsg, 3, 0); if ((is_anon && tunable_chown_uploads) || tunable_ssl_enable) { /* Need to send file descriptors to privileged broker. */ allow_nr_1_arg_match(__NR_sendmsg, 3, 0); } } if (tunable_syslog_enable) { /* The ability to pass an address spec isn't needed so disable it. We ensure * the 6th arg (socklen) is 0. We could have checked the 5th arg (sockptr) * but I don't know if 64-bit compares work in the kernel filter, so we're * happy to check the socklen arg, which is 32 bits. */ allow_nr_1_arg_match(__NR_sendto, 6, 0); } if (tunable_text_userdb_names) { reject_nr(__NR_socket, EACCES); allow_nr_2_arg_match(__NR_mmap, 3, PROT_READ, 4, MAP_SHARED); } if (tunable_write_enable) { if (!is_anon || tunable_anon_mkdir_write_enable) { allow_nr(__NR_mkdir); } if (!is_anon || tunable_anon_other_write_enable || tunable_delete_failed_uploads) { allow_nr(__NR_unlink); } if (!is_anon || tunable_anon_other_write_enable) { allow_nr(__NR_rmdir); allow_nr(__NR_rename); allow_nr(__NR_ftruncate); if (tunable_mdtm_write) { allow_nr(__NR_utime); allow_nr(__NR_utimes); } } if (!is_anon && tunable_chmod_enable) { allow_nr(__NR_chmod); } } } void seccomp_sandbox_setup_postlogin_broker() { seccomp_sandbox_setup_base(); seccomp_sandbox_setup_data_connections(); allow_nr_1_arg_match(__NR_sendmsg, 3, 0); } void seccomp_sandbox_lockdown() { size_t len = (s_syscall_index * 2) + (s_1_arg_validations * 3) + (s_2_arg_validations * 5) + (s_3_arg_validations * 7) + 5; struct sock_filter filters[len]; struct sock_filter* p_filter = filters; struct sock_fprog prog; size_t i; int ret; prog.len = len; prog.filter = filters; /* Validate the syscall architecture. */ p_filter->code = BPF_LD+BPF_W+BPF_ABS; p_filter->jt = 0; p_filter->jf = 0; /* Offset 4 for syscall architecture. */ p_filter->k = 4; p_filter++; p_filter->code = BPF_JMP+BPF_JEQ+BPF_K; p_filter->jt = 1; p_filter->jf = 0; /* AUDIT_ARCH_X86_64 */ p_filter->k = 0xc000003e; p_filter++; p_filter->code = BPF_RET+BPF_K; p_filter->jt = 0; p_filter->jf = 0; /* SECCOMP_RET_KILL */ p_filter->k = 0; p_filter++; /* Load the syscall number. */ p_filter->code = BPF_LD+BPF_W+BPF_ABS; p_filter->jt = 0; p_filter->jf = 0; /* Offset 0 for syscall number. */ p_filter->k = 0; p_filter++; for (i = 0; i < s_syscall_index; ++i) { int block_size = 1; if (s_args_3[i]) { block_size = 8; } else if (s_args_2[i]) { block_size = 6; } else if (s_args_1[i]) { block_size = 4; } /* Check for syscall number match. */ p_filter->code = BPF_JMP+BPF_JEQ+BPF_K; p_filter->jt = 0; p_filter->jf = block_size; p_filter->k = s_syscalls[i]; p_filter++; /* Check argument matches if necessary. */ if (s_args_3[i]) { p_filter->code = BPF_LD+BPF_W+BPF_ABS; p_filter->jt = 0; p_filter->jf = 0; p_filter->k = 16 + ((s_args_3[i] - 1) * 8); p_filter++; p_filter->code = BPF_JMP+BPF_JEQ+BPF_K; p_filter->jt = 0; p_filter->jf = 5; p_filter->k = s_vals_3[i]; p_filter++; } if (s_args_2[i]) { p_filter->code = BPF_LD+BPF_W+BPF_ABS; p_filter->jt = 0; p_filter->jf = 0; p_filter->k = 16 + ((s_args_2[i] - 1) * 8); p_filter++; p_filter->code = BPF_JMP+BPF_JEQ+BPF_K; p_filter->jt = 0; p_filter->jf = 3; p_filter->k = s_vals_2[i]; p_filter++; } if (s_args_1[i]) { int arg = s_args_1[i]; int code = BPF_JMP+BPF_JEQ+BPF_K; int val = s_vals_1[i]; int jt = 0; int jf = 1; if (arg > 100) { arg -= 100; code = BPF_JMP+BPF_JSET+BPF_K; val = ~val; jt = 1; jf = 0; } p_filter->code = BPF_LD+BPF_W+BPF_ABS; p_filter->jt = 0; p_filter->jf = 0; p_filter->k = 16 + ((arg - 1) * 8); p_filter++; p_filter->code = code; p_filter->jt = jt; p_filter->jf = jf; p_filter->k = val; p_filter++; } p_filter->code = BPF_RET+BPF_K; p_filter->jt = 0; p_filter->jf = 0; if (!s_errnos[i]) { /* SECCOMP_RET_ALLOW */ p_filter->k = 0x7fff0000; } else { /* SECCOMP_RET_ERRNO */ p_filter->k = 0x00050000 + s_errnos[i]; } p_filter++; if (s_args_1[i]) { /* We trashed the accumulator so put it back. */ p_filter->code = BPF_LD+BPF_W+BPF_ABS; p_filter->jt = 0; p_filter->jf = 0; p_filter->k = 0; p_filter++; } } /* No "allow" matches so kill. */ p_filter->code = BPF_RET+BPF_K; p_filter->jt = 0; p_filter->jf = 0; #ifdef DEBUG_SIGSYS /* SECCOMP_RET_TRAP */ p_filter->k = 0x00030000; #else /* SECCOMP_RET_KILL */ p_filter->k = 0; #endif ret = prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); if (ret != 0) { if (errno == EINVAL) { /* Kernel isn't good enough. */ return; } die("prctl PR_SET_NO_NEW_PRIVS"); } if (!tunable_seccomp_sandbox) { return; } #ifdef DEBUG_SIGSYS { struct sigaction sa; memset(&sa, '\0', sizeof(sa)); sa.sa_handler = handle_sigsys; sigaction(SIGSYS, &sa, NULL); } #endif ret = prctl(PR_SET_SECCOMP, 2, &prog, 0, 0); if (ret != 0) { if (errno == EINVAL) { /* Kernel isn't good enough. */ return; } die("prctl PR_SET_SECCOMP failed"); } } #else /* __linux__ && __x86_64__ */ void seccomp_sandbox_init() { } void seccomp_sandbox_setup_prelogin(const struct vsf_session* p_sess) { (void) p_sess; } void seccomp_sandbox_setup_postlogin(const struct vsf_session* p_sess) { (void) p_sess; } void seccomp_sandbox_setup_postlogin_broker() { } void seccomp_sandbox_lockdown() { } #endif /* __linux__ && __x86_64__ */ vsftpd-3.0.3/builddefs.h0000644000175000017500000000023012554101327014132 0ustar chrischris#ifndef VSF_BUILDDEFS_H #define VSF_BUILDDEFS_H #undef VSF_BUILD_TCPWRAPPERS #define VSF_BUILD_PAM #undef VSF_BUILD_SSL #endif /* VSF_BUILDDEFS_H */ vsftpd-3.0.3/Changelog0000644000175000017500000020620612554101523013643 0ustar chrischris0.0.1 initial versioned tarball released ---------------------------------------- - Added "-ldl" to LIBS to get linking to work on RedHat6.1 - Add RedHat6.1 on list of tested platforms :) 0.0.2 packaged -------------- - Emit version in greeting string - In PORT command, reject numbers <0 or >255. Problem noted by Solar Designer, - Allow an option AND a path for LIST/NLST, e.g. "LIST -al /pub". Reported by Bill Nottingham , using ncftp. Further noted by Colin Hogben using emacs and James Antill . - Don't prepend directory path for LIST (but still so for NLST). Noted by Colin Hogben and Ingo Luetkebohle - Fix problem listing non-existant or unreadable directories - just return a blank listing rather than an error. Problem noted by Martin Sillence , using squid. - Fix KDE's downloads (via KFM), it was using the "SIZE" command which I had not implemented. Reported by Simon Dales and Jo Dillon . Apparently implementing SIZE also fixed lftp's download time estimator, reported by Ingo Luetkebohle - Remove abornal_exit() from utility.c - Fix so we don't write "500 OOPS: child died" upon QUIT. Reported by Solar Designer, and Tim Bagot 0.0.3 packaged -------------- - Oops: fix so we don't emit a status 150 mark unless we actually got a connection from the client (stops some clients hanging trying to list an inaccessible directory) 0.0.4 packaged -------------- - In verbose directory listing, report symlink targets. Use the traditional syntax of: "link_name -> target_path" - Damn netscape! The comma in the response text to PASV confused it, so it had to be removed. Discovered with tcpdump! - Don't require clients to redo PORT or PASV if a RETR or STOR fails due to inability to open/create file. Fixes Netscape symlink navigation problem. - Fix for listing absolute paths with only one /, e.g. "ls /.message" was failing 0.0.5 packaged -------------- - Remove README.ftpproto - Add SECURITY/OVERVIEW - Add SECURITY/DESIGN - Note that as a security tweak, we should lose more privs if we're configured for anonymous only logins (TODO) - Add SECURITY/IMPLEMENTATION, SECURITY/TRUST, but nothing in them yet. - Convert str.c to vsf_sysutil_*. This leaves the following to do: checkauth.c, main.c, postprivparent.c, privparent.c, privsock.c, utility.c - Convert privparent.c to vsf_sysutil_*. - Create BUGS and move existing listed bugs from TODO into this new file - Add parseconf.h, parseconf.c to handle parsing of a config file (work in progress) - Fix change_full_credentials() in utility.c, to always chdir() even if we are not going to do a chroot() - Rename get_random_byte() to vsf_sysutil_get_random_byte(), and move from utility.c to sysutil.c - Create new file secutil.c, move change_full_credentials() to it and rename - Convert utility.c to vsf_sysutil_*. - handle_local_login(): don't look up username; common_do_login() does it - implement different tunable umask() values for local/anonymous users - implement SITE UMASK - implement SITE CHMOD - whoops! allow non-anonymous users to overwrite files with STOR 0.0.6 packaged -------------- - SECURITY: when in anonymous-only mode, reject usernames that aren't the anonymous usernames. This is hoping some FTP clients will be stopped from sending a cleartext password. Idea from Gerald Teschl . - Decided to put "telnet strings" on the back burner :) - Sprinkling of static in main.c - Complete parseconf.c config file parsing and plug it into main.c - Convert main.c to vsf_sysutil_*. This leaves checkauth.c, postprivparent.c and privsock.c - Now we have runtime config, make compiled in defaults extra paranoid - Implement "tunable_anon_world_readable_only" to only serve publicly readable files anonymously - Add sample "vsftpd.conf" - Eww - missing "return" in parseconf.c - Move ASCII mode transfers out of critical section in TODO - parseconf.c: if an integer starts with "0", treat it as octal - Ban "SITE CHMOD" if !tunable_write_enable - Wrote SECURITY/TRUST - Wrote SECURITY/IMPLEMENTATION, probably more to come - Update INSTALL - Add "tunable_nopriv_user" - Update parseconf.c with the two latest new config variables - Add sysdeputil.h, sysdeputil.c for system specific facilities, i.e. capabilites, authentication. - Lose checkauth.c,h - they moved into sysdeputil.c,h - Lose config.h - it moved into sysdeputil.c - Convert postprivparent.c to vsf_sysutil_* (leaves privsock.c) - Convert privsock.c to vsf_sysutil_*. All done, yay!! :) - D'oh! Missing "!" in postlogin.c refused to server publicly readable files:) - Fix chown() of uploaded files (broken initialization order in main()) - Add SPEED, and fill it with wild speculation - Rename distribution directory "vsftpd-x.x.x" (note the added "d") 0.0.7 packaged -------------- - Build with -O2 - Fix "uninitialized" warnings -O2 exposed - the one in capabilities setup could be nasty! - Nail warning in vsf_sysutil_sendfile(). We're now "-Wall warning free" - Build with -Werror to signal intent to _stay_ warning free - A few int -> long in the area of file sizes and offsets - Remove comma's at end of enum lists (-pedantic caught it) - Impact from fixing warnings caused by -pedantic - Date format %e -> %d in date display, %e isn't everywhere - Paranoia in vsf_sysutil_malloc() - Clean up interface to substring searching in str.c - Cleanups in str.c - Squash most "unsigned<->signed" conversions exposed by -Wconversion - Lose "-g" to CFLAGS; after all we're bug-free now ;-) - Add "AUDIT" - Fix up a bunch of potential 64-bit issues (maybe >2Gb files will work on 64-bit platforms now, no way to test) - Implement PR_SET_KEEPCAPS support for 2.2.18+ and 2.4.0+ kernels - In sysdeputil.c, change NULL -> 0 to help Solaris build problem - Repair vsf_sysutil_sendfile() and the caller - Logging: log the username - Logging: don't log "//" as start of filenames under certain conditions - Logging: log the date. Logging is almost useful now! - Logging: log MKD commands too; they are used in anon ftp a fair bit - Take the trouble to look into partial reads/writes. Looks like we are safe. - vsf_sysutil_read and vsf_sysutil_write now hide EINTR and retry - Replace some vsf_sysutil_{read,write} usage with vsf_sysutil_{read,write)_loop which handles partial reads and writes - Implement a sendfile() replacement for systems which lack it - Implement runtime checking for system specific Linux stuff, i.e. prctl(PR_SET_KEEPCAPS). This is inspired by RedHat7.0 headers claiming to be a 2.4.0 kernel, but actually you are running on 2.2.x! :-( - Strip the build executable at link time 0.0.8 packaged -------------- - A few incorrect sizeof()'s in postlogin.c, thanks to Antonomasia for noting these. - Decide that ASCII support isn't too important for now (waiting for users to demand it). Also decide that ABOR is a must :( Thanks to Zach Brown for the discussion. - More TODO items thanks to Stephen White - 2.0.x issues. - Provide a definition for SHUT_RDWR in sysutil.c, not all systems have that definition yet. Thanks Stephen White . - Tidy privparent.c - Decide ASCII _is_ quite important, thanks Solar ;-) - Bit of extra paranoia in sysutil.c: don't call mem*() if size == 0 - Tidy str.c - Command line: if vsftpd has an argument, it is a path to a config file. - Set TCP_NODELAY on command stream - Don't lseek() for RETR in common case with REST set to 0 - Correct error code for transfer after succesful connection (425 -> 426) - ABOR support. Bah. - APPE support (why not, it was trivial). Putting off ASCII support ;-) - Add ASCII transfer support. Bah. - Tidy up sysutil.c, fix breakage in read_loop and write_loop. 0.0.9 packaged -------------- - Remove ".message" from distribution. Thanks Mitchell Blank Jr - Note where I can get some load testing software, thanks to Dan Kegel . I'll do that soon because I hope to waste wu-ftpd. - Fix an Alpha build warning and check return value from final pam_end(). Reported by Solar Designer . - Add xinetd.d/vsftpd, from Kurt Seifried . - Integrate comments/fixes into SECURITY documentation, thanks to Antonomasia - SECURITY: default tunable_chroot_local_user to 0, because it is dangerous to give users write access to the filesystem root (think of opening trusted files relative to the root). Thanks again Solar Designer . - Add "make install" target. Currently it is minimal! - Clearer error message if vsftpd is started manually. Suggestion from Tom . - Report futuristic or old (>6 months) dates in a different format, showing the year like /bin/ls does. - Add KERNEL-2.4.0-WARNING. Whoo-hoo. Why do all my non-trivial programs seem to trigger kernel bugs? - SECURITY: refuse to allow anonymous logins if some bonehead has configured the anonymous ftp user with write access to the ftp root. - Fix ASCII downloads so that \n UNCONDITIONALLY maps to \r\n. This behaviour is now consistent with wu-ftpd and results in simpler code. - Fix ASCII uploads to not to fail to strip some \r characters. Noted by Mitchell Blank Jr . - Add TODO items: log transfer rate and anonymous password. Andrew Anderson . 0.0.10 packaged --------------- - Remove errant #include from sysutil.c. Noted by Jan-Frode Myklebust - Use gettimeofday(2) not time(2), for better resolution. - Add transfer rate to the log - Add to sysutil.c, spotted by Kevin Vajk . - Spell "LICENSE" correctly: Kevin Vajk . - Use fcntl() for locking instead of flock() because it is much more standard. flock() usage noted by Kevin Vajk . - Use more portable IPPROTO_* instead of SOL_* (IPPROTO_IP, IPPROTO_TCP). Thanks to Neil Blakey-Milner porting to FreeBSD. - Start of Solaris port, thanks to Kurt Seifried for access to a Solaris 8 box. - Portability fix: include before . - Port to Solaris 8: new directory port. New file porting_junk.h. New file solaris_bogons.h - Add vsf_findlibs.sh to cater for different platform link requirements. Now builds on Solaris and Linux with "make". - struct sockaddr casts to kill Solaris warnings. - sysdeputil.c: remove unused variable warnings. - sysutil.c: use _exit() instead of exit() to avoid libc doing stuff on exit. Fixes segfault reported by Joshua Hill . - Add BENCHMARKS. Many thanks to Andrew Anderson . - Fix disconnect/crash if SIGURG received whilst blocking on command stream. - Update INSTALL with more platforms. 0.0.11 packaged --------------- - Brag about performance in README. And why not. - Better bail-out message if the "ftp" anonymous user isn't found - Better bail-out message if the secure chroot directory isn't found - Introduce tunable_one_process_model and start work on it - Fix rare segfault on exit - race leading to infinite stack recursion - Don't bail out if we didn't get an argv[0]. Who cares? Noted by Kurt Seifried . - Change logged date format to include the year. - Add option to log in standard (wu-ftpd like) "xferlog" format. - Cater for sendfile() returning EINTR in sysdeputil.c - Use SO_LINGER on data sockets, to get accurate transfer rates! - Cater for an interrupted blocking close() - Tuning: eliminate 3 mprotect(), 1 munmap() and 1 mmap() system call per command read. - Prevent infinite loops calling sendfile(). Two bugs - we needed to check the sendfile() return for 0 (doh!!) and also, we sometimes did lseek() on a file, to beyond its end. Thanks to Daniel Veillard for reporting. - Tuning: cache fd's for /etc/passwd and /etc/group to avoid syscalls. - Tuning: "assist" the get*uid(), get*nam() calls to not make lots of useless syscalls, if /etc/group and /etc/passwd are missing. Thanks to Daniel Veillard for reporting. - Use SO_LINGER timeout of 5 mins; INT_MAX seemed to do nothing! - Finally(!) fix transfer rate timing. 0.0.12 packaged --------------- - Update INSTALL. Mention the config file can be given on the command line. - Lower VSFTP_MAX_COMMAND_LINE to 4096 (wu-ftpd uses 512 I think). - Add RedHat/vsftpd-rh7.spec, kindly provided by Emmanuel Galanos . - Add more RedHat/* spec files etc, kindly provided by Andrew Anderson . - Cleanup: move two process model code to "twoprocess.c". - Damn! Make the file lock _block_ if it's busy, in sysutil.c. - Finish implementing one process model - benchmarks to follow - Don't log success if the download is ABOR'ed during the blocking close(). - Build on systems without PAM (obviously local logins won't work..) - Beware of FreeBSD accept() bug: ai32@drexel.edu - Implemented a customizable ftp banner with "ftpd_banner" config file setting - Builds on OpenBSD 2.8 - woohoo - FreeBSD: look for libpam.so* in /usr/lib - FreeBSD: add #include otherwise CMSG_* break. - Kill privparent.[ch] - merged them into twoprocess.c - Enable SIGCHLD handler _before_ forking - should nail a race which could lead to zombies. Inspired by zombie report from Joe Klemmer . - Data connection timeout code. - ftpcmdio.c: Don't cancel the alarm when we get a command. For safety, we insist that that the only way to "cancel" the alarm is to reset it. This prevents hangs blocking on write() to the command stream. Of course, data transfers are long running operations and have their own timeouts. - Data transfer timeout now kills session. - Take care that no writes block once we've decided to abandon ship. - FreeBSD sendfile() support. I wonder if it works! 0.0.13 packaged --------------- - Split out directory listing code into ls.c - Change blocking accept() and connect() code to use select() not SIGALRM! - Remove alarm() timeout junk from file locking in logging.c - Cater for signals interrupting the blocking file lock - Whoops: fix data timeout incorrectly going off. Noted and fixed by Joshua Hill . - Implement tunable_pasv_promiscuous to relax PASV IP checks. Useful if you are playing with secure tunneling of command connection. Idea, patch from Seth Vidal . - Much better line-by-line file reading string buffer functions. - Use the above better functions for directory messages and config file reading. This eliminates a probable quadratic algorithm, i.e. it's a speedup. - Explictly free certain buffers rather than using the static trick. For example, the config file buffer which is only used once. - Massive cleanup and refactoring of login code. - Add ability to specify file containing list of banned e-mail addresses for anonymous users. Apparently a required feature for big sites trying to avoid DDoS attacks. - Add ability to specify file containing list of users to chroot(), request from helo , who also persuaded me not to use the homedir hack in /etc/passwd. - Add TODO: PASV port range config setting, for firewalled setups. From Rafal Wojtczuk . - Rudimentary support for non-PAM local user authentication, with encouragement and helpful discussion from D Richard Felker III . - Use MAP_ANON instead of mmap() /dev/zero for anonymous pages. It saves using a file descriptor. Neither are standard(?) but MAP_ANON seems to work on a superset of systems compared with mmap() /dev/zero. - Ability to specify a PASV local port range with pasv_min_port and pasv_max_port. Request from Rafal Wojtczuk . - Non-PAM authentication: check /etc/shells, and support shadow password and account expiry. - First cut at a vsftpd.conf man page! (vsftpd.conf.5) 0.0.14 packaged --------------- - Default to ASCII mode transfers, as per RFC. Bug noted with Macintosh client by William Day . - Implement "ls -a". - Implement "ls -r". - Implement "ls -l", i.e. "NLST -L" now works - Implement "ls -t". Superb - now the oft-used "ls -ltr" works! - setproctitle() support - FreeBSD only in the first cut. - setproctitle() on Linux support - what a hack! This crap really needs kernel support. I'm ashamed I bothered. - Repair the contributed spec files a bit, based on reports from Oleg Drokin and Jakob Lichtenberg . - Show remote IP and local username in setproctitle() support. - Add vsftpd.8 man page, thanks to Daniel Jacobowitz . - In sysdeputil.c, check macros LINUX_VERSION_CODE and KERNEL_VERSION are defined. From James Antill . - Workaround a broken firewall that expects a very precise PASV response. We now match wu-ftpd. Many many thanks to Jakob Lichtenberg for his help. - If tunable_anon_world_readable_only (default), don't list directories unless they are world readable. - Use qsort() for directory sorting - eliminates gross quadratic sorting. Turbo charges directory listings with 1000's of entries. - Fix big memory leak in str_list_free(). - Simplify + reduce heap usage in strlist.c - Optimize away lots of excessive heap usage and redundant copying in str.c - By default, show numeric user/group id's in directory listings. Makes generating directory listings perhaps 4 times(!) faster, and is noticeable with e.g. 5000 entries in a directory. n.b. this performance figure is as measured on a glibc-2.2 system, so glibc would seem to be inefficient. - Don't use MSG_DONTWAIT - prefer the more portable fcntl()/O_NONBLOCK. Fixes glibc-2.0 build issues. - Work around broken Linux-2.0 unix fd passing. Now builds/runs on RH5.2. - Build fixes for FreeBSD 3.5, with help from Jerry Walsh . - Only restrict directory listings to world-readable for _anonymous_ users! Thanks again Jerry Walsh for the report. - Add TUNING - Special case for security/performance: if we need _no_ privilege, then force one process model. Security: root dropped totally straight away. Performance: no messing around forking etc. - Minor performance tweaks, don't leave big mappings lying around from config file parsing. 0.0.15 packaged --------------- - Argh. Fix SuSE 6.0 build issue (time_t used but not defined). Reported by Peter Stern . - Another SuSE 6.0 issue - another damn system lacking CMSG_SPACE etc. - Cope with any return value from blocking close(2). Previously, we missed EAGAIN, which some systems might return (not Linux). - New wizzy synchronous signal framework, to prevent re-entrancy issues. It presents an interface very similar to the traditional UNIX async interface. Technically this is a security fix; imagine a SIGURG (user controllable!) coming in whilst we are deep inside glibc. The SIGURG handler is non-trivial and may well re-enter and upset glibc. Specific example: the malloc subsystem. - When handing SIGURG, account the time taken under the data tranfer timeout. - Install the command timeout handler before we write anything to the remote. - Cleanup capabilities handling to be taken care of in secutil.c. - Fix bug: one_process_model mode could lose supplementary groups. - Add "SIZE" file. - Make one_process_model work with the anon deny e-mail list. - Massive cleanups. Start moving static state into a session structure. - Oops - fix Solaris 8 build by fixing include order in porting_junk.h, and include a dirfd() replacement. Noted by William Yodlowsky and Mike Batchelor . - Fix return of a void function call in a void function. It upsets Sun's compiler. (gcc is fine with it, I'm not sure if it's against the rules). Noted by Mike Batchelor . - Make it possible to use port ranges starting lower than 5001, from Matthew Kirkwood . - Use a /dev/zero mmap() fallback if we do not find MAP_ANON. This should fix the build on Solaris 2.6, 2.7 machines. Reported by Mike Batchelor . Also noted as one of the problems facing an IRIX build. - Add MDTM support, so clients like ncftp can set the date on downloaded files. - Add irix_bogons.h, trying to port to IRIX 6.5, with help from Jan-Frode Myklebust . - Don't reference "struct msghdr.msg_flags", not all systems have it. Clear it with vsf_sysutil_memclr() instead. Found on IRIX 6.5.11 - Cater for systems lacking getusershell(), e.g. IRIX 6.5.11, by not using it. - Fix compiler error with header files claiming 2.4 headers but only having 2.2 headers. Reported by Ben Ricker . - Kill warning on system without capabilities. - Add -R option to ls (disabled by default), to cater for broken clients which assume it is present (e.g. mirror). - Add "Makefile.sun", from Mike Batchelor . - Fix PORT transfer crashes with "one_process_model". Reported by Andrew Anderson . - Cater for HP-UX shared libraries which end in ".sl", from Kevin Vajk . - Add hpux_bogons.h, and make MAP_ANON a synonym for MAP_ANONYMOUS. - Move send_fd and recv_fd to sysdeputil.c and provide old-style fd passing code for IRIX and HP-UX. - Get it going on HP-UX 11.11 and HP-UX 10.20, thanks to Kevin Vajk . Minor changes to hpux_bogons.h - Update vsftpd.conf with "ls_recurse_enable". - Get it going on IRIX 6.5.11, thanks to Jan-Frode Myklebust . - Fix reporting of filenames in MKD operations (regression since 0.0.15). - Wow - lots of contributed .spec files. Adopt those from Seth Vidal . - Fix FreeBSD build. 0.9.0 packaged -------------- - Fix .spec files to include URL, from Seth Vidal . - Don't let unprintable characters escape into setproctitle(). Thanks to Solar Designer for the suggestion. - Make the PAM service name a tunable, suggestion from Solar Designer. - Add option to log all FTP protocol (log_ftp_protocol). - Log logins, successful or failed. - Refuse to download a file in ASCII mode if REST position != 0. Solar reminded me by looking in the BUGS file. - Clearly mark an ASCII download in the FTP response string. - Argh. Fix broken upload timeout again (goes off erroneously). - Fix logging of FTP protocol, add logging of pid. Reported by Frank Fiamingo . - Fix bug where logging code bug()'s on the second logged operation, iff logging is in fact disabled! Reported by Alexander Schreiber . - From Solar: be paranoid about libc implementations of isprint() in sysutil.c - Careful not to write any unprintable characters into the log. - fchmod() files that we fchown(), to prevent suid games, etc. - Cleanups, added comments to some headers. - Minor speedups to some str.c string handling functions. - Joe Klemmer reports zombies again! Nail a couple of races: make the SIGCHLD handler async, and cater for an interrupted wait(2) syscall. - If chroot_local_user=YES then chroot_list_enable becomes a list of users to NOT chroot(). With input from Lars Hecking . 0.9.1 packaged -------------- - DAMN! Fix silly "missing newline" logging bug. 0.9.1 repackaged ---------------- - Refuse to start if local_enable and anonymous_enable are NO, hit by Lars Hecking . - Report anonymous e-mail in the LOGIN log event, idea from Joachim Blaabjerg . - Fix man page install in vsftpd-rh7.spec, from Matthew Galgoci . - Fix chown_upload bug noted by brett . - Add concept of guest user, idea from Andrew Anderson . - Simple bandwidth limitation, inspired by Mads Martin Jørgensen . - Fix chown_upload bug in a different way. - Correct *_umask details in vsftpd.conf.5, from brett . - Don't show .files unless "ls -a" was specified, n.b. this differs in behaviour from wu-ftpd, but not proftpd. - Implement directory write(2) buffering, for a 33% reduction in CPU used to send big dirs. Activate the bandwidth limit on directory listings. - HPUX enhancements: setproctitle and sendfile. Thanks to Kevin Vajk . - We DON'T need to follow symlinks on "ls -R" - phew. - Add README.solaris. Thanks to Mike Batchelor . - Implement passing remote host to PAM (for pam_access etc.), thanks to Emmanuel Galanos . - Fix guest_enable so that this means all non-anonymous users are guest users. - Add ability to deny selected users before they get the chance to send their cleartext password!! - Fix FreeBSD build - use a cast instead of floor() which needs libm. 0.9.2 packaged -------------- - Fix potential leak in PAM handling code. - Fix build in the non-PAM case (dammit!!). Reported by Alexey E. Korchagin and Michael Fengler . - Include filename and size in bytes in the "here comes the data" 150 message. - Change link flags from "-s" to "-Wl,-s" - Add libcap support - should fix ia64, Alpha build problems with syscalls. - Tidy up vsf_findlibs.sh - Work with NFS mounted home dirs and root_squash, thanks to Hunter Matthews for the report. - Add FAQ. - Improve "make install". - Fix Solaris build (nanosleep is in a separate library, typical). - Fix REST + STOR combination, investigation inspired by Mike Batchelor . 0.9.3 packaged -------------- - Update xinetd file to reflect /usr/local location. Thanks to Fridtjof Busse . - Make our 150 response code match wu-ftpd - allows broken "ange-ftp" of emacs to do a percentage complete indicator. Reported by Jonathan Kamens via Andrew Anderson . - Fix build on S390, ia64 platforms (poor kernel includes). Patch from . - Fix up vsf_findlibs.sh to cater for RedHat7.2 which has libcap. Reported by Chris Burton . - Boast some more in BENCHMARKS. - Add anon_root and local_root, inspired by Ole Tange . - Fix up vsf_findlibs.sh to cater for broken Mandrake, and also consider the case of missing PAM headers (no pam-devel installed). Thanks to Jeff Baldwin for access to Mandrake. At this point: 1.0.0 packaged and released ------------------------------------------ Ah, the wonderful psychology of release numbers ----------------------------------------------- - Fix IRIX build (capabilities issue), Jan-Frode Myklebust . - Fix FreeBSD build, reported by Jim Breton . - Fix Debian build, reported by Brian Clark . 1.0.1 packaged -------------- - Fix .spec files to use /usr/local/sbin not /usr/sbin, noted by Bill Unruh . - Small doc tweaks and improvements(?) - Add COPYING, the GNU GPL version 2. - Add use_localtime config option to override the use of GMT times. - Add tunable_check_shell (default YES) so people can disable this if they are not using PAM. - AIX 5.1 build support, thanks to Jan-Frode Myklebust . - Add "hide_ids" option to show user/group in directory listings as "ftp". Request from Solar. - Use the seemingly more portable setreuid() and setregid(), poxy HP. - Use status 550 instead of 500 for known but disabled commands. - Rename "dirchange.[ch]" to "banner.[ch]". - Multiline connect banner support via "banner_file" config option. - Minor error message changes. - Add more FAQ entries. - Add patch to specify PASV address - thanks to Mike McLean . - Drop the 2.4.0 kernel warning file - Rudimentary standalone listener support - to be expanded in a later release. - If sendfile() returns EINVAL just fall back to normal routines - handles non-pagecache backed files. - Add "port_promiscuous" setting - should help enabling FXP. - Modify anon_root and local_root to change directory _before_ applying the chroot(). - Open all files O_NONBLOCK to avoid pipes blocking on open. - Support wu-ftpd style per-user chroot() via /./ in /etc/passwd HOMEDIR. - Add SIGHUP support to new built in listener. - Per-user config overrides, via "user_config_dir" - woohoo! - Warning fixes, i.e. change "index" to "indexx" thanks to Olaf Kirch . - Make sure the standalone daemon doesn't leak zombies! - Supposedly fix kernel messages about MSG_PEEK race - thanks to advice from Alexey . - Add global client limit for standalone mode. - Add username that failed when we die with str_getpwnam. - Add a bunch of documentation under EXAMPLES. At this point: 1.1.0 package released ------------------------------------- (Note - 1.1.0 also included large file (>2Gb) support). - Fix port_promiscuous, oops! Thanks to Bjørn-Ove Heimsund . - Fix to support umasks which create executable files. Reported by "Martin, Andreas" . - Make the messages more.. professional :( Thanks to Steven G. Taylor . - Allow anon users to append to files if they can delete files! Suggestion from Michael Leuchtenburg . - Hopefully fix Solaris build (-lresolv) - Replace atoll() with a homebrew - modern FreeBSD, OpenBSD lack it. - Different solution for a umask which creates executable files: file_open_mode. - First attempt at Tru64 build, working with . - A few minor FAQ additions. - Change date format in the log from Sep 09 -> Sep 9. Avoids breaking some broken log parsers. - Make "INSTALL" better and clearer. - Fix passwd_chroot_enable, reported by James Jones . - Finish Tru64 building :-) - Add tunable_no_anon_password as asked for by Stephen Quinney . At this point: 1.1.1 package released ------------------------------------- - Add per-IP connection limits in standalone mode. - Add logging of refused connect due to global or IP connection limits. - (Many thanks for testing and suggestions from Rob van Nieuwkerk and Adrian Reber . - Make connection limit exceeded messages nonblocking. - Don't exit the listener if fork fails. At this point: 1.1.2 package released ------------------------------------- - Support for tcp_wrappers. - First stab at Solaris sendfilev() support. - Don't bomb out the listener on SIGHUP if the config became invalid. - End vsf_findlibs.sh with "exit 0;" - thanks Lars Hecking ! - Integrate with tcp_wrappers - load config based on VSFTPD_LOAD_CONF environment variables. Allows per-IP configurability in standalone mode. - Fix build without tcp_wrappers. - Fix Solaris sendfilev() support - interruption via a signal returns EINTR rather than a partial byte count! - Add to EXAMPLE/ - PER_IP_CONFIG and INTERNET_SITE_NOINETD At this point: 1.1.3 package released ------------------------------------- - Eliminate crypt() not defined warning. - "grep -q" is not standard to redirect to /dev/null instead. - Make banned_email_file work second time around. - Add force_dot_files to work around broken clients. The behaviour when enabled is very wu-ftpd like. - Implement SITE HELP - should work around IE bug? - Update README, vsftpd.conf with references to read the manual page! - Log revamp: add dual_log_enable to log to xferlog AND vsftpd.log. - Log revamp: add syslog_enable to log vsftpd.log to syslog(). - Add "background" option to background the listener process. - Fix warning is vsftpd.8 man page, Bill Nottingham . - Fix tcp wrappers support to NOT emit loads of Bad file descriptor messages to the system log. - Add ability to make bandwidth limiter smoother by using e.g. trans_chunk_size=8192. - Add ability for virtual users to use local privs non anon privs, via virtual_use_local_privs=YES. - Fix sendfile() fallback on FreeBSD, thanks to Adam Stroud . - Add pam_session support, as well as utmp and wtmp logging for local logins (when using a PAM build). Tested pam_limits maxlogins works. - Ensure the source IP address for PORT connects is always the same as the control connection local IP address. Previously it was not when NOT using connect_from_port_20 in the presence of multiple local IP addresses. - Oops - make max_per_ip and max_clients work with the two process model when both connect_from_port_20 and chown_uploads are false. - Initial IPv6 support (EPSV only). - Add EPRT support to IPv6. - Fix "ls .file" to list .file even if the ls -a flag is not present. Noted by and thanks to Sean Millichamp . - Better error messages for config file parse fail: include setting name. - Fix bug in str_split_text where text is greater than 1 character long! - Make it build on Solaris8 - switch from utmp to utmpx and handle missing LOG_FTP. - Always check for VSFTPD_LOAD_CONF environment variable. - Implement HELP properly (should help broken clients). - Fix FreeBSD build (no utmpx.h, so disable feature). - Fix chown_uploads. - "Guess fix" for FreeBSD reported bug. I reckon FreeBSD is returning -EINTR from a blocking close but still closing the fd, despite the error return. So cater for this. Reported by Drew Vogel . - Add download_enable and dirlist_enable. Useful in conjunction with the per-user config stuff. - Add chmod_enable. - Implement STRU and MODE for _old_, broken clients! - Log connects. - Fix 500 OOPS with chown_uploads and an APPE command. - Improve some error messages: die -> die2 for more information. - Repair max_per_ip (problem comparing IPv4 addresses). - Make chown_uploads work with virtual users. - Chmod files to 0600 before chown_uploads kicks in. - Add STOU support. - Add cmds_allowed config parameter. - Add some FAQ entries. At this point: v1.2.0 released! =============================== - Apply NetBSD patch to sysdeputil.c to activate a few features. Thanks to Lubomir Sedlacik . - Apply fix for broken clients that terminate commands with \r\r\n. Thanks to Andrey Chernomyrdin . - AIX send_file support, thanks to Tomas Ogren . - Fix typos in vsftpd.conf.5, thanks to SEKINE Tatsuo . - Simple -F flag support to LIST and NLST. Needed for some broken clients. - Add simple ? wildcard in pattern matching. - Make pasv_min_port and pasv_max_port work if they are the same value. Thanks to Marvin Solomon . - Paranoia: ignore user_config_dir if username has a / in it. - Implement stub ALLO command to keep busybox/ftpput happy. - Implement REIN, ACCT and SMNT stubs. - Implement FEAT along with an OPTS stub. - Implement STAT (no-args version). - Implement STAT (file/dir). - Add very simple access control via hide_file and deny_file. These should NOT be used for securing content as they are very dumb! Filesystem permissions are still the recommended way for securing important content. - Allow unsetting of string values with option= (i.e. blank). - Default virtual users to being chroot()'ed to the guest_user's home directory, if virtual_use_local_privs is not set. - Add support for "user_sub_token", where you can set the home directory of guest_user to "/home/virtual/$USER", and "user_sub_token" to "$USER" to have a root directory auto generated based on username logging in, e.g. fred logs in and gets chroot()'ed in /home/virtual/fred. - Fix bug in str_replace_text if replace token matches at end of string. - Recognize P@SW as PASV; works around an SMC router bug. - Accept an async ABOR sequence if it arrives via non-urgent data. Fixes issue with Cisco routers. Thanks to Eddie Corns . - Implement simple {,} support in pattern matcher (nested not handled). Handy to use with hide_file and deny_file options. (v1.2.1pre2) - Fix port range with pasv_min_port and pasv_max_port to use the full range (the upper limit wasn't being used very often!). - Activate SO_REUSEADDR on passive listen sockets - makes servers with restricted port ranges much more useable! - Add secure_email_list_enable, to provide simple anonymous password control. For some cases, it's better than the hassle of virtual users. Idea thanks to Malcolm O'Callaghan, . - Add some FAQ entries. (v1.2.1pre3) - Fix issue with failure to call openlog() before using tcp_wrappers. Part of RH bugzilla #89765. (The more serious part was fixed with v1.2.0). At this point: v1.2.1 released! =============================== - Fix FreeBSD 5.1/5.2 issue with time_t being long long on that platform. Thanks to Matthias Andree . - Tweak vsftpd.conf.5 to avoid automated mails from ESR ;-) - Remove vsftpd spec files, they are old and buggy. - Add -v flag which just outputs the version and exits. - Fix nasty issue resulting in listener instability under extreme load (root cause was re-entering malloc/free). Many thanks to Olivier Baudron for an excellent report. (v1.2.2pre1) - Fix build with modern glibc-2.3 and no libcap on Linux. - Fix 64-bit file support on Solaris. (v1.2.2pre2) - Add initial support for running as the user which launched vsftpd, i.e. no root needed. Warning - easy to create insecurity if you use this without knowing what you are doing. - For above run-as-launching-user support: make CDUP re-use CWD code so that deny_file of *..* is useful. - Attempt fix of 64-bit file support on FreeBSD (may need another go). - Update INSTALL to refer to more modern platforms. At this point: v1.2.2 released! (need to get the listener fix out) ================================================================== - Improve logging (log deletes, renames, chmods, etc. as requested by users). - Add no_log_lock to work around Solaris / Veritas locking hangs. - Add EPRT, EPSV, PASV and TVFS to FEAT response. - Implement use of MDTM to set timestamps. - Recognize FEAT prior to login. - Add OpenSSL (AUTH TLS / SSL) support for encrypted control and data connections! Hurrah. - Increase max size of .message files to 4000 characters, thanks to Eric Pancer for the report. - Add easy builddefs.h ability to disable PAM builds even when PAM is installed. - Report vsftpd version in STAT output. - Add REFS file. - Change parent<->child socket comms from DGRAM to STREAM for increased reliability. The main benefit is should the parent be killed (or crash out) then the child won't block on a read() that will never return. - Make str_reserve reserve space for the trailing zero as well, so we don't cause a reallocation if we exactly fill the buffer. - Optimize the sending of strings over the parent<->child comms links. - Improve the build system so tcp_wrappers, PAM and OpenSSL can be forcibly compiled out. - Fix vsftpd.conf.5 typos, thanks to Dmitry V. Levin . - If trans_chunk_size is between 1 and 4096, use 4096 rather than ignoring totally. Thanks to Brad . - Lose Makefile.sun and README.solaris special cases. - Add SSL / TLS info to SECURITY texts. - Add README.ssl - Add documentation for new SSL options to vsftpd.conf.5. - Add support for CWD ~ (and in general support ~ at start of any filename). Also support stuff like ~chris/pics, if tilde_user_enable=YES is set. Note that all of this is for very very broken clients :-( - Fix compile warnings. - Update INSTALL with (recent) OS X as a working platform. At this point: v2.0.0 released! =============================== - Add -lcrypto for the SSL build; needed for some systems! Thanks to Nelson Chang . - Oops; fix session bale out if an empty length password is given. - Fix build on Fedora Core 2 (-lcap cannot seem to find /lib/libcap.so). - Fix vsftpd.conf.5 man page error in "ssl_sslv3", thanks to Etienne Chevillard . - Clarify licensing: I allow linking of my GPL software with the OpenSSL libraries. Thanks to Jonas Bofjall . - Add COPYRIGHT. - Fix build on OpenBSD, FreeBSD, probably NetBSD too (they aren't SuSv2 compliant; timezone should be a variable not a function). - Fix build where PAM build is enabled but PAM headers are missing. - Fix build on RHEL3 (remove errant include from twoprocess.c). At this point: v2.0.1 released! =============================== - Fix FAQ typo, thanks to Jose Santiago Oyervides Gonzalez . - Emit data transfer status messages (success / failure) after flushing and waiting for the full data transfer to reach the client. This should help work around buggy FTP clients such as FlashFXP, which is known to truncate files incorrectly. (v2.0.2pre1) - Make str_empty actually allocate an empty string. - Change the ASCII receive code to ONLY rip out \r if it is just before a \n; someone finally complained about this. (v2.0.2pre2) - Enable AIX Large File Support #define from Tomas gren . - Add a couple of FAQ entries. - Fix time delta code areas to cope with negative deltas, which will occur if the clock is adjusted backwards. Thanks to Andrew Anderson for a great report. - Fix "errno" checks to be robust in multiple places; previously, calls to failing library calls could be made inbetween the original library call and the "errno" reads. Thanks to Andrew Anderson for a great report. - Make bandwidth limiter work with SSL data connections. (v2.0.2pre3) - Note that the SSL / bandwidth limiter bug fixed a much more serious bug: SSL data connection dropouts after data_connection_timeout seconds. - Typo fixes. At this point: v2.0.2 released! (need to get the SSL dropout fix out) ===================================================================== - Document what regex expressions are supported in the man page. - New settings rsa_private_key_file and dsa_private_key_file to allow separate files for the certificates and private keys. - Initial, simple fix for timed out processes not exiting when SSL is in use. Better fix (which reports timeout to client properly) to follow. - Add which setsockopt option failed to die("setsockopt") calls. - Fix when running on recent OpenBSDs - OpenBSD change broke vsftpd. Lower linger timeout from INT_MAX to 32767 (SHORT_MAX). Reported by Ewoud van der Vliet and Ed Vazquez . (v2.0.3pre1) - Fix error with IPv4 connections to IPv6 listeners and PORT type data connections when connect_from_port_20 is set. RedHat bugzilla 134541. Reported by Joe Orton , Radek Vokal and Andreas Kupfer . - Remove vsf_sysutil_sockaddr_same_family (unused). - Support protocol 1 (IPv4) in EPRT. - Add ssl.c to AUDIT. - Allow config file to use "ssl_ciphers=" to use default OpenSSL cipher list. - Allow "EPSV 1" to mean IPv4 EPSV. - Report dummy IP but correct port with IPv6 / PASV. - Handle SSL_WANT_READ and SSL_WANT_WRITE retries in SSL_read and SSL_write; fixes SSL upload failures when data timeouts are in use with some clients. Specifically, I used the test case FileZilla 2.2.12a on Windows XP. Reported by Lee Lawrence (using CuteFTP and BackupEdge) and Christian DELAIR (using lftp, FileZilla and SmartFTP). Thanks to these two people for valuable help. (v2.0.3pre2) - Implicitly disable connect_from_port_20 and chown_uploads when a non-root user is using run_as_launching_user. - Add force_anon_logins_ssl and force_anon_data_ssl for a fully SSL secure anonymous-only solution (useful when you don't have root access and a range of acceptable anonymous passwords as credentials). - Use SSL BIO callbacks to fix data connection timeout checks; the checks weren't all occurring promply. At this point: v2.0.3 released! (need to get about three imporant fixes out) ============================================================================ - Add explicit "This FTP server does not allow anonymous logins" message. - Add paranoid checks to sysutil.c for large values / lengths. - Fix incorrect comment about ASCII and SIZE in the vsftpd.conf example. - Load per-IP config files earlier; allows more settings to be tuned on a per-IP level. Suggested by Reber Tobias . - Fix MDTM on non-existant files. Reported by Ken A . - {} regex fix so that {*} correctly matches everything. Reported by Tom Van de Wiele . - Add "mdtm_write" option to disable MDTM being able to set file timestamps. - Fix HPUX build, thanks to Kevin Vajk . - Add optional file locking support via lock_upload_files (default on). - Apply LDFLAGS patch from Mads Martin Joergensen . - Add pasv_addr_resolve option to allow pasv_address to get DNS resolved once at startup. - Apply patch to fix timezone issues (caused by chroot() interacting badly with newer glibc versions). Thanks to Dmitry V. Levin and Mads Martin Joergensen . At this point: v2.0.4 released! =============================== - Apply fix for O_NONBLOCK vs. XFS DMAPI filesystem. Thanks to Sudha Srinivasan . - Fix build warnings exposed by my upgrade to Fedora Core 5 / GCC4.1.1. - Be more honest in FEAT response if PORT or PASV are disabled! Reported by Charles Honton . Allows MS Explorer to get the transfer mode correct. - pam_pwdb.so -> pam_unix.so in example PAM file. Thanks to Rhodes, Colin . - Add FAQ issue regarding "chroot fails with SSL" - in fact, sshd is being hit here instead ;-) - Minor man page doc tweaks. - Tiny bit of paranoia in privops.c. - Revert change to reject anonymous logins before asking for password. This fixes complaints about IE not showing the FTP login dialog. - Change SSL certificate load to cater for chaining too. - Added delay_failed_login and delay_successful_login to help limit resources taken by brute force attacks. - Kick session after a few login fails. Allows IP blocking solutions to be more immediately effective. - Replace setenv() with more portable putenv(). First part of Solaris fix. - Replace tm_gmtoff usage with timezone and daylight. Second part of Solaris fix. - Set PAM items TTY and RUSER if possible. - OpenBSD build warning fixes. - So, timezone and daylight are not available on BSD, so redo the whole TZ thing again. Should use only very portable constructs now. At this point: v2.0.5 released! =============================== - Fix delay_failed_login typo. Oops. - Patch the getcwd and readlink sysutil helpers to reflect that they wouldn't like a 0-sized buf. No caller is affected. Thanks Ilja van Sprundel . - Allow a (fake) reauth as the same user as the logged in user. Should resolve .NET related report from Sabo Jim . - Tweak from Lucian Adrian Grijincu to take unnecessary port calculations out of a loop. - Fix byte I/O accounting in the error path of do_file_send_rwloop, thanks to . - Don't log FireFox's attempts to RETR directories! Reported by Nixdorf, Tim . - Fix STOU sending the same 150 status line twice - oops! Reported by . - Fix xferlog format for virtual (guest) users, reported by Andy Fletcher . - Fix bug with empty user list file and userlist_deny=NO. Reported by Marcin Zawadzki/GlobalVanet.com . - Pretend we have proper UTF8 support and respond positively to OPTS UTF8 ON. Thanks Stanislav Maslovski . - Add control over the file permissions used in the chown()ing of anonymous uploads: chown_upload_mode (default 0600 as before). Suggestion from An Pham . - Do a retry getting the active ftp socket in vsf_privop_get_ftp_port_sock(); should help buggy Solaris systems. Reported by Michael Masterson . - Add debug_ssl option to dump out some SSL connection details. - Use code 522, not 521, to indicate that the server requires an encrypted data connection. Still does not seem to coax lftp to retry :( - Recognize OPTS pre-login. - A whole ton of SSL improvements, including ability to force requirement of a client cert; data and control channel client cert cross checking. Ability to require fully valid / authentic client certs. No cert-based auth yet. - Change my e-mail to my GMail account. At this point: v2.0.6 released! =============================== - Fix finding libcap for the link on Slackware systems, thanks to Roman Kravchenko . - Fix build on Solaris 2.8 due to non-standard C, thanks to IIDA Yosiaki . - Fix man page typo, thanks Matt Selsky . - Bring the PASV listen() into the bind() retry loop to resolve a race under extreme load. Thanks to Curtis Taylor . - Enhance logging for debug_ssl. - Shutdown the SSL data connections properly. This prevents clients such as recent FileZilla from complaining. Reported by various people. - Add option to enforce proper SSL shutdown on uploads. Left it off after much agonizing because clients are so broken in this area. - Add option to delete failed uploads. At this point: v2.0.7 released! =============================== - Remove .postlogin.c.swp (thanks Kaibin Li )! - findlibs repairs for libcap; builds on my Ubuntu 6.06 again. - Apply patch to fix "error: assignment of read-only member '__in'" build error on broken systems where the WIFEXITED() etc. macros write to their argument. Thanks Ingo Terpelle . - Replace spaces in xferlog with underscores, report from Michael Wittauer . - Reload default config values before re-parsing config file on SIGHUP. This makes the values correct in the case a setting was removed from the file. - Do not issue an FTP response for a blank line on the control channel. Fixes issues with some broken NAT devices. ProFTPd does the same, so hopefully nothing will break. Report from Frank Bulk . - Replace usage of broken _syscall() with syscall(). Fixes build errors for those without libcap-devel installed. - Add implicit SSL support with implicit_ssl option. - Remove arbitrary restriction on one process model + SSL. - Set a session ID on the SSL context. - Add the skeleton of a built-in ptrace sandbox. Not yet useful for anything other than catching compile errors. Yes, I'm crazy :P - Use PR_SET_PDEATHSIG all over the place so that when the listener is killed, existing sessions are booted too. - Use SSL_peek; makes SSL pipelining work. Note that I never found any SSL client that need it, but still a nice code clean-up. - Change ASCII download behaviour so \r\n does not become \r\r\n. This mirrors proftpd behaviour instead of wu-ftpd. Thanks Paul Abel . - Switch all sighandlers to the synchronous ones. Prevents us having to block and unblock signals all the time. - Add a "use alarm" option to synchronous signal handlers, to ensure the race condition against a blocking call does not result in a permanent non-delivery. - Use SIGTERM for privileged parent process shutdown, so they can still update u/wtmp properly. - Do RAND_load_file from /dev/urandom in the child context because I don't trust the OpenSSL API vs. fork(). Different children do have different RNG state; this is defense in depth. - More thoroughly close the remote ends of the priv_sock, ensuring that child death results in no blocking in the parent. This is a matter of tidyness; the SIGCHLD handler will reliably tear down the parent. - Do the same for the SSL slave / consumer channels. - Fix OpenBSD build. - Move SSL data handling into the SSL slave process. Incurs some extra overhead in terms of context switches and copies, but it enables this next item: - By default, require SSL data connections to exhibit SSL session re-use of the control channel. Unlike the cert thing, this _is_ something we can turn on by default as most clients seem to do reuse. Yay. - Change 522 response for SSL connection fail to note when session reuse is required. (v2.1.0pre1 here) - More work on the inbuilt ptrace()-sandbox support. - Clear the idle alarm when starting data transfer if there is no data alarm. - Fix syslog format; don't embed 2nd copy of date, pid. Thanks to René Berber . - Lock file before truncating it for upload. Fixes various simultaneous upload corruption issues. - Make sure to give 426 error code on uploads if ABOR was received. - Add cmds_denied option to complement cmds_allowed. - Ignore lines in config file containing only white space. - Require write_enable / anon_upload_enable / etc. to process STOU. - FC10 patch (vsftpd-1.0.1-missingok.patch): tweak to logrotate file. - FC10 patch (vsftpd-1.2.1-nonrootconf.patch): bail if the config file is not owned by the currently running user. - FC10 patch (vsftpd-2.0.1-tcp_wrappers.patch): explicitly call openlog() to avoid syslog() bug where some settings are not initialized. - FC10 patch (vsftpd-2.0.3-daemonize_fds.patch): when backgrounding, replace fd 0,1,2 with /dev/null fd. - FC10 patch (vsftpd-2.0.5-correct_comments.patch): comment tweaks in the sample config file. - FC10 patch (vsftpd-2.0.5-fix_unique.patch): use the default filename given by STOU if it is available. - FC10 patch (vsftpd-2.0.5-pam_end.patch): call pam_end() properly so modules can act on errors if they want. - FC10 patch (vsftpd-2.0.5-pasv_dot.patch): Strict RFC compliance for PASV command; add a trailing period. - FC10 patch (vsftpd-2.0.5-uname_size.patch): allow longer usernames. It's not 1990 any more, so trust PAM etc. to not stack-buffer-overflow. - FC10 patch (vsftpd-2.0.5-underscore_uname.patch): permit username to start with underscore or period. - FC10 patch (vsftpd-2.0.6-listen.patch): default listen to YES. - Fix crash on SIGHUP introduced in 2.1.0pre1. Oops. - FC10 patch (vsftpd-2.0.5-bind_denied.patch): retry PASV bind() on EACCES too, which can happen on SELinux systems. - Default resource limit for child processes: 100MB address space. - Finishing touches to the initial sandbox policy; only permit connect() to the host on the control channel being the nicest touch. (v2.1.0pre2 here) - Fix 64-bit build (oops)! Thanks Martin Nagy . - Fix config of SSL built in; not enabled; two process model. Report from Martin. - Shutdown the command connection in the priv parent's SIGTERM handler; kills of children where the PR_SET_PDEATHSIG cannot due to different user ids. (v2.1.0pre3 here) - Fix build on FC10. - Some FAQ tweaks. - Permit fcntl(F_GETFD) in sandbox policy. Needed for FC10. Not sure where it comes from but it is harmless. (My guess would be glibc-2.9's new support for using O_CLOEXEC more). - Fix build warning on 64-bit. - Fix build on OpenBSD again. (v2.1.0pre4 here) - Bring userlist_deny handling inside the max_login_fail accounting. At this point: v2.1.0 released! =============================== - Apply Tavis' RLIMIT_NOFILE trick in the twoprocess model's initial unprivileged child. - Fix build error due to __NR_utimes. - Ugh. Can't use RLIMIT_NOFILE in the SSL case because the process later receives data transfer fd's via recvmsg(). It's a total shame because doing the SSL handshake under even lower privs would be a real boost. (v2.1.1pre1 here) - Fix some declarations occuring in the middle of a block; broke older more strict compilers. - Handle the case where libcap is now libcap.so.2; fixes build on my new Ubuntu 9.04. - Enhance 522 error message to point to require_ssl_reuse option. - Fix NASTY regression whereby data transfer timeouts would fire incorrectly under SSL transfers. In addition, the transfer rate caps were not working under SSL transfers. Reported by several people. - Use the login delay machinery for userlist-based denials too. Thanks to Tomas Hoger for the patch. - Fix another tedious regression whereby absent per-user config files were causing a session fail rather than being gracefully ignored. - Use the somewhat new CLONE_NEWPID / CLONE_NEWIPC to provide more isolation in the vsftpd low-priv processes (CLONE_NEWNET pending). - Use RLIMIT_NPROC to disallow fork()ing etc. in processes that do not need to create new ones. - Add "isolate" config flag to disable the new weird clone() flags if necessary. At this point: v2.1.1 released! =============================== - Fix compile error in sysdeputil.c on some Linux systems. At this point: v2.1.2 released! =============================== - Fix compile on systems with no RLIMIT_NPROC, oops. - Change some unsigned int to socklen_t's to avoid warnings on various platforms, e.g. AIX. - Add some syscall constants to ptracesandbox.c to fix the build on systems with 2.4 kernel headers. - Look for libs in /lib64 and /usr/lib64 too (Fixes Fedora 11 x86_64 compile). - Fix EACCES mapping, thanks Solar Designer . - Dont emit a bogus "OOPS: " message upon a QUIT, report from Solar Designer . - Tweak example vsftpd.conf to add commented out chroot_local_user=YES, from Ivan I. Grushin . - Where available, use CLONE_NEWNET to isolate the untrusted processes so that they can't do arbitrary connect() and instead have to ask the privileged process for sockets. Moderate code disturbance - hope for no breakage :-/ - Disable implicit activation of one_process_model so that an anonymous setup can benefit from the no-network isolation of the unprivileged process (where available). (vsftpd-2.2.0pre1) - Call pam_get_item(PAM_USER) after authentication in case a PAM module remapped the username. Based on a patch from John McNair . - Apply a couple of IPv6 fixes from Corinna Schultz , particularly when MS operating systems are talking on link local addresses. - Handle the error case for accepting a PASV connection in the two process model properly. - Pull in a couple of minor tidyup patches from Openwall. - Add "-o" command line option to specify option, e.g. vsftpd -olisten=NO. Also respect ordering with respect config files, e.g. vsftpd -olisten=NO /etc/vsftpd.conf -olocal_enable=NO Inspiration from Solar / Openwall. (vsftpd-2.2.0pre2) - Revert listen to be "NO" by default and also set max_per_ip / max_clients to 2000 / 50 as a default. Thanks to Solar. - Sanity check for admin: check config files are regular files. - Error out if opening the per-user config file gives an error other than ENOENT. Thanks Solar (who is on fire today ;-) (vsftpd-2.2.0pre3) - Apply some typo fixes from Solar. - Error out on read() errors when reading config files. (vsftpd-2.2.0pre4) - Add tunable_isolate_network to parseconf.c, thanks Stefan Pfetzing . - Don't try and use CLONE_NEWPID etc. if we get EPERM -- may happen even to root if using linux-vserver. Also thanks to Stefan Pfetzing . - Couple of typo tweaks from Cristi Terpea . - Change // style comments to /* style. - Fix pointer aliasing issue in new PAM_USER item support, thanks to Solar. At this point: v2.2.0 released! =============================== - Apply patch to set SSL context timeout to maximum, from Tim Kosse . Should prevent data connection failures after a long transfer or idle period. - Apply async-safety signal tweaks from Solar. - Fix crash regression with the pasv_address option enabled. - Typo fix and look for libnsl in /lib64, from Robby Workman . - When asking the client to send us a cert, make sure we broadcast some suitable certificates. Fixes compatibility with the z/OS FTP client when tunable_ssl_request_cert=YES. - Rip out the silly "cached time" concept. Simplifies things and fixes incorrect transfer time bug reported by Rajeev V. Pillai. Explicitly pass around the cached current time in seconds in the one place that needs to avoid repeated gettimeofday() calls (directory listing). - Do the login fail delay in the parent process for cases where we checked a username / password. At this point: v2.2.1 released! =============================== - Change "File receive OK." to "Transfer complete." to placate some broken clients. Thanks Holger Kiehl . - Fix erroneous "child died" upon FTP client connect, when under load. Awesome thanks to Holger Kiehl for running diagnostic tests on his live server. - Boot the session if an overly long line is encountered. (vsftpd-2.2.2pre1) At this point: v2.2.2 released! =============================== - Add extremely simply HTTP support. It's very experimental, ignorant of HTTP protocol and headers, and likely has all sorts of other issues. The use case it might satisfy is if you need to serve simple static unathenticated content with large levels of paranoia. - Fix port_promiscuous breakage. Report from Soeren . (v2.3.0pre1) - Minor FAQ update. - Use a larger address space limit if using text_userdb_names=YES - Always use CLONE_NEWNET if possible when in HTTP mode. - Change REST + STOR so that it's possible to overwrite part of file without truncating it. (v2.3.0pre2) - Boot the session if we see a USER where encryption was required. May prevent the transmission of plaintext passwords by buggy clients. Idea from Marcin Hlybin . - Fix failure to transmit a large ASCII file over SSL, if it contains \n -> \r\n fixups. At this point: v2.3.0 released! =============================== - Fix silly regression re: log files being overwritten from the start. - Rename a few file-open functions to make it clearer what they do. At this point: v2.3.1 released! =============================== - Argh! Fix version number. At this point: v2.3.2 released! =============================== - Avoid consuming excessive CPU when matching filenames to patterns. Thanks to Maksymilian Arciemowicz . - Some bugfixes from Raphaël Rigo -- good bugs but no apparent security impact. At this point: v2.3.3 released! =============================== - Fix compile. Extreme suckage. At this point: v2.3.4 released! =============================== - Try and force glibc to cache zoneinfo files in an attempt to work around glibc parsing vulnerability. Thanks to Kingcope. - Only report CHMOD in SITE HELP if it's enabled. Thanks to Martin Schwenke . - Some simple fixes and cleanups from Thorsten Brehm . - Only advertise "AUTH SSL" if one of SSLv2, SSLv3 is enabled. Thanks to steve willing . - Handle connect() failures properly. Thanks to Takayuki Nagata . - Add stronger checks for the configuration error of running with a writeable root directory inside a chroot(). This may bite people who carelessly turned on chroot_local_user but such is life. At this point: v2.3.5 released! =============================== - Update vsf_findlibs.sh to work on Ubuntu 11.10+ - Make listen mode the default. - Add -Werror to build flags. - Fix missing "const" in ssl.c - Add seccompsandbox.c to support a seccomp filter sandbox; works against Ubuntu 12.04 ABI. - Rearrange ftppolicy.c a bit so the syscall list is easily comparable with seccompsandbox.c - Rename deprecated "sandbox" to "ptrace_sandbox". - Add a few more state checks to the privileged helper processes. - Add tunable "seccomp_sandbox", default on. - Use hardened build flags. Distros of course override these and provide their own build flags but no harm in showing how it could be done. - Retry creating a PASV socket upon port reuse race between bind() and listen(), patch from Ralph Wuerthner . - Don't die() if recv() indicates a closed remote connection. Problem report on a Windows client from Herbert van den Bergh, . - Add new config setting "allow_writeable_chroot" to help people in a bit of a spot with the v2.3.5 defensive change. Only applies to non-anonymous. - Remove a couple of fixed things from BUGS. - strlen() trunction fix -- no particular impact. - Apply some tidyups from mmoufid@yorku.ca. (vsftpd-3.0.0-pre1) - Fix delete_failed_uploads if there is a timeout. Report from Alejandro Hernández Hdez . - Fix other data channel bugs such as failure to log failure upon timeout. - Use exit codes a bit more consistently. - Fix bad interaction between SSL and trans_chunk_size. - Redo data timeout to fire properly for SSL sessions. - Redo idle timeout to fire properly for SSL sessions. - Make sure PROT_EXEC isn't allowed, thanks to Will Drewry for noticing. - Use 10 minutes as a max linger time just in case an alarm gets lost. (vsftpd-3.0.0-pre2) - Change PR_SET_NO_NEW_PRIVS define, from Kees Cook. - Add AES128-SHA to default SSL cipher suites for FileZilla compatibility. Unfortunately the default vsftpd SSL confiuration still doesn't fully work with FileZilla, because FileZilla has a data connection security problem: no client certificate presentation and no session reuse. At least the error message is now very clear. - Add restart_syscall to seccomp policy. Triggers reliably if you strace whilst a data transfer is in progress. - Fix delete_failed_uploads for anonymous sessions. - Don't listen for urgent data if the control connection is SSL, due to possible protocol synchronization issues. At this point: v3.0.0 released! =============================== - Fix some seccomp related build errors on certain CentOS and Debian versions. - Seccomp filter sandbox: missing munmap() -- oops. Did you know that qsort() opens and maps /proc/meminfo but only for larger item counts? - Seccomp filter sandbox: deny socket() gracefully for text_userdb_names. - Fix various NULL crashes with nonsensical config settings. Noted by Tianyin Xu . - Force cast to unsigned char in is* char functions. - Fix harmless integer issues in strlist.c. - Started on a (possibly ill-advised?) crusade to compile cleanly with Wconversion. Decided to suspend the effort half-way through. At this point: v3.0.1 released! =============================== - One more seccomp policy fix: mremap (denied). - Support STOU with no filename, uses a STOU. prefix. At this point: v3.0.2 released! =============================== - Increase VSFTP_AS_LIMIT to 200MB; various reports. - Make the PWD response more RFC compliant; report from Barry Kelly . - Remove the trailing period from EPSV response to work around BT Internet issues; report from Tim Bishop . - Fix syslog_enable issues vs. seccomp filtering. Report from Michal Vyskocil . At least, syslogging seems to work on my Fedora now. - Allow gettimeofday() in the seccomp sandbox. I can't repro failures, but I probably have a different distro / libc / etc. and there are multiple reports. - Some kernels support PR_SET_NO_NEW_PRIVS but not PR_SET_SECCOMP, so handle this case gracefully. Report from Vasily Averin . - List the TLS1.2 cipher AES128-GCM-SHA256 as first preference by default. - Make some compile-time SSL defaults (such as correct client shutdown handling) stricter. - Disable Nagle algorithm during SSL data connection shutdown, to avoid 200ms delays. From Tim Kosse . - Kill the FTP session if we see HTTP protocol commands, to avoid cross-protocol attacks. A report from Jann Horn . - Kill the FTP session if we see session re-use failure. A report from Tim Kosse . (vsftpd-3.0.3pre1) - Enable ECDHE, Tim Kosse . - Default cipher list is now just ECDHE-RSA-AES256-GCM-SHA384. - Minor SSL logging improvements. - Un-default tunable_strict_ssl_write_shutdown again. We still have tunable_strict_ssl_read_eof defaulted now, which is the important one to prove upload integrity. (vsftpd-3.0.3pre2) At this point: v3.0.3 released! =============================== vsftpd-3.0.3/TUNING0000644000175000017500000000235510750743701012765 0ustar chrischrisSo, you want vsftpd to go quickly? Here are some random assorted performance tips. 1) vsftpd thrives because of its lightweight RSS and vm usage. If you run a glibc based system (e.g. RedHat 5+), look in /etc/nsswitch.conf, and if possible, disable the "nis" and "nisplus" options for "passwd", "shadow" and "group". This prevents unneeded runtime libraries being added into the vsftpd virtual memory space. 2) vsftpd will attempt to save CPU power by using sendfile() on capable operating systems. Currently, Linux 2.2+ and FreeBSD 3.0+ use sendfile(). Consider running on these excellent operating systems. 3) Irritated by vsftpd using _two_ processes per connection? Don't be, it's a very secure architecture. However, if you run Linux 2.4+, or Linux 2.2.19+, a "one process" security model is possible thanks to nifty security features. See the vsftpd.conf man page. 4) Avoid large directories (e.g. thousands of entries) if possible. Many filesystems do not handle such cases efficiently at all. Preparing large directory listings will require vsftpd to use moderate amounts of memory and CPU. If you _must_ have large directories, consider either making them unreadable, or use a filesystem which copes well with large directories such as reiserfs. vsftpd-3.0.3/twoprocess.c0000644000175000017500000003733012025276245014414 0ustar chrischris/* * Part of Very Secure FTPd * License: GPL v2 * Author: Chris Evans * twoprocess.c * * Code implementing the standard, secure two process security model. */ #include "twoprocess.h" #include "privops.h" #include "prelogin.h" #include "postlogin.h" #include "postprivparent.h" #include "session.h" #include "privsock.h" #include "secutil.h" #include "filestr.h" #include "str.h" #include "sysstr.h" #include "utility.h" #include "tunables.h" #include "defs.h" #include "parseconf.h" #include "ssl.h" #include "readwrite.h" #include "sysutil.h" #include "sysdeputil.h" #include "sslslave.h" #include "seccompsandbox.h" static void drop_all_privs(void); static void handle_sigchld(void* duff); static void handle_sigterm(void* duff); static void process_login_req(struct vsf_session* p_sess); static void common_do_login(struct vsf_session* p_sess, const struct mystr* p_user_str, int do_chroot, int anon); static void handle_per_user_config(const struct mystr* p_user_str); static void calculate_chdir_dir(int anon, struct mystr* p_userdir_str, struct mystr* p_chroot_str, struct mystr* p_chdir_str, const struct mystr* p_user_str, const struct mystr* p_orig_user_str); static void handle_sigchld(void* duff) { struct vsf_sysutil_wait_retval wait_retval = vsf_sysutil_wait(); (void) duff; /* Child died, so we'll do the same! Report it as an error unless the child * exited normally with zero exit code */ if (vsf_sysutil_retval_is_error(vsf_sysutil_wait_get_retval(&wait_retval))) { die("waiting for child"); } else if (!vsf_sysutil_wait_exited_normally(&wait_retval)) { die("child died"); } vsf_sysutil_exit(0); } static void handle_sigterm(void* duff) { (void) duff; /* Blow away the connection to make sure no process lingers. */ vsf_sysutil_shutdown_failok(VSFTP_COMMAND_FD); /* Will call the registered exit function to clean up u/wtmp if needed. */ vsf_sysutil_exit(1); } void vsf_two_process_start(struct vsf_session* p_sess) { vsf_sysutil_install_sighandler(kVSFSysUtilSigTERM, handle_sigterm, 0, 1); /* Overrides the SIGKILL setting set by the standalone listener. */ vsf_set_term_if_parent_dies(); /* Create the comms channel between privileged parent and no-priv child */ priv_sock_init(p_sess); if (tunable_ssl_enable) { /* Create the comms channel between the no-priv SSL child and the low-priv * protocol handling child. */ ssl_comm_channel_init(p_sess); } vsf_sysutil_install_sighandler(kVSFSysUtilSigCHLD, handle_sigchld, 0, 1); { int newpid; if (tunable_isolate_network) { newpid = vsf_sysutil_fork_newnet(); } else { newpid = vsf_sysutil_fork(); } if (newpid != 0) { priv_sock_set_parent_context(p_sess); if (tunable_ssl_enable) { ssl_comm_channel_set_consumer_context(p_sess); } /* Parent - go into pre-login parent process mode */ while (1) { process_login_req(p_sess); } } } /* Child process - time to lose as much privilege as possible and do the * login processing */ vsf_set_die_if_parent_dies(); priv_sock_set_child_context(p_sess); if (tunable_ssl_enable) { ssl_comm_channel_set_producer_context(p_sess); } if (tunable_local_enable && tunable_userlist_enable) { int retval = -1; if (tunable_userlist_file) { retval = str_fileread(&p_sess->userlist_str, tunable_userlist_file, VSFTP_CONF_FILE_MAX); } if (vsf_sysutil_retval_is_error(retval)) { die2("cannot read user list file:", tunable_userlist_file); } } drop_all_privs(); seccomp_sandbox_init(); seccomp_sandbox_setup_prelogin(p_sess); seccomp_sandbox_lockdown(); init_connection(p_sess); /* NOTREACHED */ } static void drop_all_privs(void) { struct mystr user_str = INIT_MYSTR; struct mystr dir_str = INIT_MYSTR; unsigned int option = VSF_SECUTIL_OPTION_CHROOT | VSF_SECUTIL_OPTION_NO_PROCS; if (!tunable_ssl_enable) { /* Unfortunately, can only enable this if we can be sure of not using SSL. * In the SSL case, we'll need to receive data transfer file descriptors. */ option |= VSF_SECUTIL_OPTION_NO_FDS; } if (tunable_nopriv_user) { str_alloc_text(&user_str, tunable_nopriv_user); } if (tunable_secure_chroot_dir) { str_alloc_text(&dir_str, tunable_secure_chroot_dir); } /* Be kind: give good error message if the secure dir is missing */ { struct vsf_sysutil_statbuf* p_statbuf = 0; if (vsf_sysutil_retval_is_error(str_lstat(&dir_str, &p_statbuf))) { die2("vsftpd: not found: directory given in 'secure_chroot_dir':", tunable_secure_chroot_dir); } vsf_sysutil_free(p_statbuf); } vsf_secutil_change_credentials(&user_str, &dir_str, 0, 0, option); str_free(&user_str); str_free(&dir_str); } void vsf_two_process_login(struct vsf_session* p_sess, const struct mystr* p_pass_str) { char result; priv_sock_send_cmd(p_sess->child_fd, PRIV_SOCK_LOGIN); priv_sock_send_str(p_sess->child_fd, &p_sess->user_str); priv_sock_send_str(p_sess->child_fd, p_pass_str); priv_sock_send_int(p_sess->child_fd, p_sess->control_use_ssl); priv_sock_send_int(p_sess->child_fd, p_sess->data_use_ssl); result = priv_sock_get_result(p_sess->child_fd); if (result == PRIV_SOCK_RESULT_OK) { /* Miracle. We don't emit the success message here. That is left to * process_post_login(). * Exit normally, unless we are remaining as the SSL read / write child. */ if (!p_sess->control_use_ssl) { vsf_sysutil_exit(0); } else { ssl_slave(p_sess); } /* NOTREACHED */ } else if (result == PRIV_SOCK_RESULT_BAD) { /* Continue the processing loop.. */ return; } else { die("priv_sock_get_result"); } } int vsf_two_process_get_priv_data_sock(struct vsf_session* p_sess) { char res; unsigned short port = vsf_sysutil_sockaddr_get_port(p_sess->p_port_sockaddr); priv_sock_send_cmd(p_sess->child_fd, PRIV_SOCK_GET_DATA_SOCK); priv_sock_send_int(p_sess->child_fd, port); res = priv_sock_get_result(p_sess->child_fd); if (res == PRIV_SOCK_RESULT_BAD) { return -1; } else if (res != PRIV_SOCK_RESULT_OK) { die("could not get privileged socket"); } return priv_sock_recv_fd(p_sess->child_fd); } void vsf_two_process_pasv_cleanup(struct vsf_session* p_sess) { char res; priv_sock_send_cmd(p_sess->child_fd, PRIV_SOCK_PASV_CLEANUP); res = priv_sock_get_result(p_sess->child_fd); if (res != PRIV_SOCK_RESULT_OK) { die("could not clean up socket"); } } int vsf_two_process_pasv_active(struct vsf_session* p_sess) { priv_sock_send_cmd(p_sess->child_fd, PRIV_SOCK_PASV_ACTIVE); return priv_sock_get_int(p_sess->child_fd); } unsigned short vsf_two_process_listen(struct vsf_session* p_sess) { priv_sock_send_cmd(p_sess->child_fd, PRIV_SOCK_PASV_LISTEN); return (unsigned short) priv_sock_get_int(p_sess->child_fd); } int vsf_two_process_get_pasv_fd(struct vsf_session* p_sess) { char res; priv_sock_send_cmd(p_sess->child_fd, PRIV_SOCK_PASV_ACCEPT); res = priv_sock_get_result(p_sess->child_fd); if (res == PRIV_SOCK_RESULT_BAD) { return priv_sock_get_int(p_sess->child_fd); } else if (res != PRIV_SOCK_RESULT_OK) { die("could not accept on listening socket"); } return priv_sock_recv_fd(p_sess->child_fd); } void vsf_two_process_chown_upload(struct vsf_session* p_sess, int fd) { char res; priv_sock_send_cmd(p_sess->child_fd, PRIV_SOCK_CHOWN); priv_sock_send_fd(p_sess->child_fd, fd); res = priv_sock_get_result(p_sess->child_fd); if (res != PRIV_SOCK_RESULT_OK) { die("unexpected failure in vsf_two_process_chown_upload"); } } static void process_login_req(struct vsf_session* p_sess) { enum EVSFPrivopLoginResult e_login_result = kVSFLoginNull; char cmd; /* Blocks */ cmd = priv_sock_get_cmd(p_sess->parent_fd); if (cmd != PRIV_SOCK_LOGIN) { die("bad request"); } /* Get username and password - we must distrust these */ { struct mystr password_str = INIT_MYSTR; priv_sock_get_str(p_sess->parent_fd, &p_sess->user_str); priv_sock_get_str(p_sess->parent_fd, &password_str); p_sess->control_use_ssl = priv_sock_get_int(p_sess->parent_fd); p_sess->data_use_ssl = priv_sock_get_int(p_sess->parent_fd); if (!tunable_ssl_enable) { p_sess->control_use_ssl = 0; p_sess->data_use_ssl = 0; } e_login_result = vsf_privop_do_login(p_sess, &password_str); str_free(&password_str); } switch (e_login_result) { case kVSFLoginFail: priv_sock_send_result(p_sess->parent_fd, PRIV_SOCK_RESULT_BAD); return; break; case kVSFLoginAnon: str_free(&p_sess->user_str); if (tunable_ftp_username) { str_alloc_text(&p_sess->user_str, tunable_ftp_username); } common_do_login(p_sess, &p_sess->user_str, 1, 1); break; case kVSFLoginReal: { int do_chroot = 0; if (tunable_chroot_local_user) { do_chroot = 1; } if (tunable_chroot_list_enable) { struct mystr chroot_list_file = INIT_MYSTR; int retval = -1; if (tunable_chroot_list_file) { retval = str_fileread(&chroot_list_file, tunable_chroot_list_file, VSFTP_CONF_FILE_MAX); } if (vsf_sysutil_retval_is_error(retval)) { die2("could not read chroot() list file:", tunable_chroot_list_file); } if (str_contains_line(&chroot_list_file, &p_sess->user_str)) { if (do_chroot) { do_chroot = 0; } else { do_chroot = 1; } } str_free(&chroot_list_file); } common_do_login(p_sess, &p_sess->user_str, do_chroot, 0); } break; case kVSFLoginNull: /* Fall through */ default: bug("weird state in process_login_request"); break; } /* NOTREACHED */ } static void common_do_login(struct vsf_session* p_sess, const struct mystr* p_user_str, int do_chroot, int anon) { int was_anon = anon; const struct mystr* p_orig_user_str = p_user_str; int newpid; vsf_sysutil_install_null_sighandler(kVSFSysUtilSigCHLD); /* Tells the pre-login child all is OK (it may exit in response) */ priv_sock_send_result(p_sess->parent_fd, PRIV_SOCK_RESULT_OK); if (!p_sess->control_use_ssl) { (void) vsf_sysutil_wait(); } else { p_sess->ssl_slave_active = 1; } /* Handle loading per-user config options */ handle_per_user_config(p_user_str); /* Set this before we fork */ p_sess->is_anonymous = anon; priv_sock_close(p_sess); priv_sock_init(p_sess); vsf_sysutil_install_sighandler(kVSFSysUtilSigCHLD, handle_sigchld, 0, 1); if (tunable_isolate_network && !tunable_port_promiscuous) { newpid = vsf_sysutil_fork_newnet(); } else { newpid = vsf_sysutil_fork(); } if (newpid == 0) { struct mystr guest_user_str = INIT_MYSTR; struct mystr chroot_str = INIT_MYSTR; struct mystr chdir_str = INIT_MYSTR; struct mystr userdir_str = INIT_MYSTR; unsigned int secutil_option = VSF_SECUTIL_OPTION_USE_GROUPS | VSF_SECUTIL_OPTION_NO_PROCS; /* Child - drop privs and start proper FTP! */ /* This PR_SET_PDEATHSIG doesn't work for all possible process tree setups. * The other cases are taken care of by a shutdown() of the command * connection in our SIGTERM handler. */ vsf_set_die_if_parent_dies(); priv_sock_set_child_context(p_sess); if (tunable_guest_enable && !anon) { p_sess->is_guest = 1; /* Remap to the guest user */ if (tunable_guest_username) { str_alloc_text(&guest_user_str, tunable_guest_username); } p_user_str = &guest_user_str; if (!tunable_virtual_use_local_privs) { anon = 1; do_chroot = 1; } } if (do_chroot) { secutil_option |= VSF_SECUTIL_OPTION_CHROOT; } if (!anon) { secutil_option |= VSF_SECUTIL_OPTION_CHANGE_EUID; } if (!was_anon && tunable_allow_writeable_chroot) { secutil_option |= VSF_SECUTIL_OPTION_ALLOW_WRITEABLE_ROOT; } calculate_chdir_dir(was_anon, &userdir_str, &chroot_str, &chdir_str, p_user_str, p_orig_user_str); vsf_secutil_change_credentials(p_user_str, &userdir_str, &chroot_str, 0, secutil_option); if (!str_isempty(&chdir_str)) { (void) str_chdir(&chdir_str); } str_free(&guest_user_str); str_free(&chroot_str); str_free(&chdir_str); str_free(&userdir_str); p_sess->is_anonymous = anon; seccomp_sandbox_init(); seccomp_sandbox_setup_postlogin(p_sess); seccomp_sandbox_lockdown(); process_post_login(p_sess); bug("should not get here: common_do_login"); } /* Parent */ priv_sock_set_parent_context(p_sess); if (tunable_ssl_enable) { ssl_comm_channel_set_producer_context(p_sess); } /* The seccomp sandbox lockdown for the priv parent is done inside here */ vsf_priv_parent_postlogin(p_sess); bug("should not get here in common_do_login"); } static void handle_per_user_config(const struct mystr* p_user_str) { struct mystr filename_str = INIT_MYSTR; struct vsf_sysutil_statbuf* p_statbuf = 0; struct str_locate_result loc_result; int retval; if (!tunable_user_config_dir) { return; } /* Security paranoia - ignore if user has a / in it. */ loc_result = str_locate_char(p_user_str, '/'); if (loc_result.found) { return; } str_alloc_text(&filename_str, tunable_user_config_dir); str_append_char(&filename_str, '/'); str_append_str(&filename_str, p_user_str); retval = str_stat(&filename_str, &p_statbuf); if (!vsf_sysutil_retval_is_error(retval)) { /* Security - file ownership check now in vsf_parseconf_load_file() */ vsf_parseconf_load_file(str_getbuf(&filename_str), 1); } else if (vsf_sysutil_get_error() != kVSFSysUtilErrNOENT) { die("error opening per-user config file"); } str_free(&filename_str); vsf_sysutil_free(p_statbuf); } static void calculate_chdir_dir(int anon_login, struct mystr* p_userdir_str, struct mystr* p_chroot_str, struct mystr* p_chdir_str, const struct mystr* p_user_str, const struct mystr* p_orig_user_str) { if (!anon_login) { const struct vsf_sysutil_user* p_user = str_getpwnam(p_user_str); if (p_user == 0) { die2("cannot locate user entry:", str_getbuf(p_user_str)); } str_alloc_text(p_userdir_str, vsf_sysutil_user_get_homedir(p_user)); if (tunable_user_sub_token) { str_replace_text(p_userdir_str, tunable_user_sub_token, str_getbuf(p_orig_user_str)); } } if (anon_login && tunable_anon_root) { str_alloc_text(p_chroot_str, tunable_anon_root); } else if (!anon_login && tunable_local_root) { str_alloc_text(p_chroot_str, tunable_local_root); if (tunable_user_sub_token) { str_replace_text(p_chroot_str, tunable_user_sub_token, str_getbuf(p_orig_user_str)); } } /* If enabled, the chroot() location embedded in the HOMEDIR takes * precedence. */ if (!anon_login && tunable_passwd_chroot_enable) { struct str_locate_result loc_result; loc_result = str_locate_text(p_userdir_str, "/./"); if (loc_result.found) { str_split_text(p_userdir_str, p_chdir_str, "/./"); str_copy(p_chroot_str, p_userdir_str); } } } vsftpd-3.0.3/main.c0000644000175000017500000002412012025252441013111 0ustar chrischris/* * Part of Very Secure FTPd * Licence: GPL v2 * Author: Chris Evans * main.c */ #include "session.h" #include "utility.h" #include "tunables.h" #include "logging.h" #include "str.h" #include "filestr.h" #include "ftpcmdio.h" #include "sysutil.h" #include "sysdeputil.h" #include "defs.h" #include "parseconf.h" #include "oneprocess.h" #include "twoprocess.h" #include "standalone.h" #include "tcpwrap.h" #include "vsftpver.h" #include "ssl.h" /* * Forward decls of helper functions */ static void die_unless_privileged(void); static void do_sanity_checks(void); static void session_init(struct vsf_session* p_sess); static void env_init(void); static void limits_init(void); int main(int argc, const char* argv[]) { struct vsf_session the_session = { /* Control connection */ 0, 0, 0, 0, 0, /* Data connection */ -1, 0, -1, 0, 0, 0, 0, /* Login */ 1, 0, INIT_MYSTR, INIT_MYSTR, /* Protocol state */ 0, 1, INIT_MYSTR, 0, 0, /* HTTP hacks */ 0, INIT_MYSTR, /* Session state */ 0, /* Userids */ -1, -1, -1, /* Pre-chroot() cache */ INIT_MYSTR, INIT_MYSTR, INIT_MYSTR, INIT_MYSTR, 1, /* Logging */ -1, -1, INIT_MYSTR, 0, 0, 0, INIT_MYSTR, 0, /* Buffers */ INIT_MYSTR, INIT_MYSTR, /* Parent <-> child comms */ -1, -1, /* Number of clients */ 0, 0, /* Home directory */ INIT_MYSTR, /* Secure connection state */ 0, 0, 0, 0, 0, INIT_MYSTR, 0, -1, -1, /* Login fails */ 0 }; int config_loaded = 0; int i; tunables_load_defaults(); /* This might need to open /dev/zero on systems lacking MAP_ANON. Needs * to be done early (i.e. before config file parse, which may use * anonymous pages */ vsf_sysutil_map_anon_pages_init(); /* Argument parsing. Any argument not starting with "-" is a config file, * loaded in the order encountered. -o opt=value options are loading in the * order encountered, including correct ordering with respect intermingled * config files. * If we see -v (version) or an unknown option, parsing bails and exits. */ if (argc == 0) { die("vsftpd: missing argv[0]"); } for (i = 1; i < argc; ++i) { const char* p_arg = argv[i]; if (p_arg[0] != '-') { config_loaded = 1; vsf_parseconf_load_file(p_arg, 1); } else { if (p_arg[1] == 'v') { vsf_exit("vsftpd: version " VSF_VERSION "\n"); } else if (p_arg[1] == 'o') { vsf_parseconf_load_setting(&p_arg[2], 1); } else { die2("unrecognise option: ", p_arg); } } } /* Parse default config file if necessary */ if (!config_loaded) { struct vsf_sysutil_statbuf* p_statbuf = 0; int retval = vsf_sysutil_stat(VSFTP_DEFAULT_CONFIG, &p_statbuf); if (!vsf_sysutil_retval_is_error(retval)) { vsf_parseconf_load_file(VSFTP_DEFAULT_CONFIG, 1); } vsf_sysutil_free(p_statbuf); } /* Resolve pasv_address if required */ if (tunable_pasv_address && tunable_pasv_addr_resolve) { struct vsf_sysutil_sockaddr* p_addr = 0; const char* p_numeric_addr; vsf_sysutil_dns_resolve(&p_addr, tunable_pasv_address); vsf_sysutil_free((char*) tunable_pasv_address); p_numeric_addr = vsf_sysutil_inet_ntop(p_addr); tunable_pasv_address = vsf_sysutil_strdup(p_numeric_addr); vsf_sysutil_free(p_addr); } if (!tunable_run_as_launching_user) { /* Just get out unless we start with requisite privilege */ die_unless_privileged(); } if (tunable_setproctitle_enable) { /* Warning -- warning -- may nuke argv, environ */ vsf_sysutil_setproctitle_init(argc, argv); } /* Initialize the SSL system here if needed - saves the overhead of each * child doing this itself. */ if (tunable_ssl_enable) { ssl_init(&the_session); } if (tunable_listen || tunable_listen_ipv6) { /* Standalone mode */ struct vsf_client_launch ret = vsf_standalone_main(); the_session.num_clients = ret.num_children; the_session.num_this_ip = ret.num_this_ip; } if (tunable_tcp_wrappers) { the_session.tcp_wrapper_ok = vsf_tcp_wrapper_ok(VSFTP_COMMAND_FD); } { const char* p_load_conf = vsf_sysutil_getenv("VSFTPD_LOAD_CONF"); if (p_load_conf) { vsf_parseconf_load_file(p_load_conf, 1); } } /* Sanity checks - exit with a graceful error message if our STDIN is not * a socket. Also check various config options don't collide. */ do_sanity_checks(); /* Initializes session globals - e.g. IP addr's etc. */ session_init(&the_session); /* Set up "environment", e.g. process group etc. */ env_init(); /* Set up resource limits. */ limits_init(); /* Set up logging - must come after global init because we need the remote * address to convert into text */ vsf_log_init(&the_session); str_alloc_text(&the_session.remote_ip_str, vsf_sysutil_inet_ntop(the_session.p_remote_addr)); /* Set up options on the command socket */ vsf_cmdio_sock_setup(); if (tunable_setproctitle_enable) { vsf_sysutil_set_proctitle_prefix(&the_session.remote_ip_str); vsf_sysutil_setproctitle("connected"); } /* We might chroot() very soon (one process model), so we need to open * any required config files here. */ /* SSL may have been enabled by a per-IP configuration.. */ if (tunable_ssl_enable) { ssl_init(&the_session); ssl_add_entropy(&the_session); } if (tunable_deny_email_enable) { int retval = -1; if (tunable_banned_email_file) { retval = str_fileread(&the_session.banned_email_str, tunable_banned_email_file, VSFTP_CONF_FILE_MAX); } if (vsf_sysutil_retval_is_error(retval)) { die2("cannot read anon e-mail list file:", tunable_banned_email_file); } } if (tunable_banner_file) { int retval = str_fileread(&the_session.banner_str, tunable_banner_file, VSFTP_CONF_FILE_MAX); if (vsf_sysutil_retval_is_error(retval)) { die2("cannot read banner file:", tunable_banner_file); } } if (tunable_secure_email_list_enable) { int retval = -1; if (tunable_email_password_file) { retval = str_fileread(&the_session.email_passwords_str, tunable_email_password_file, VSFTP_CONF_FILE_MAX); } if (vsf_sysutil_retval_is_error(retval)) { die2("cannot read email passwords file:", tunable_email_password_file); } } if (tunable_run_as_launching_user) { tunable_one_process_model = 1; if (!vsf_sysutil_running_as_root()) { tunable_connect_from_port_20 = 0; tunable_chown_uploads = 0; } } if (tunable_one_process_model) { vsf_one_process_start(&the_session); } else { vsf_two_process_start(&the_session); } /* NOTREACHED */ bug("should not get here: main"); return 1; } static void die_unless_privileged(void) { if (!vsf_sysutil_running_as_root()) { die("vsftpd: must be started as root (see run_as_launching_user option)"); } } static void do_sanity_checks(void) { { struct vsf_sysutil_statbuf* p_statbuf = 0; vsf_sysutil_fstat(VSFTP_COMMAND_FD, &p_statbuf); if (!vsf_sysutil_statbuf_is_socket(p_statbuf)) { die("vsftpd: not configured for standalone, must be started from inetd"); } vsf_sysutil_free(p_statbuf); } if (tunable_one_process_model) { if (tunable_local_enable) { die("vsftpd: security: 'one_process_model' is anonymous only"); } if (!vsf_sysdep_has_capabilities_as_non_root()) { die("vsftpd: security: 'one_process_model' needs a better OS"); } } if (!tunable_local_enable && !tunable_anonymous_enable) { die("vsftpd: both local and anonymous access disabled!"); } if (!tunable_ftp_enable && !tunable_http_enable) { die("vsftpd: both FTP and HTTP disabled!"); } if (tunable_http_enable && !tunable_one_process_model) { die("vsftpd: HTTP needs 'one_process_model' for now"); } } static void env_init(void) { vsf_sysutil_make_session_leader(); /* Set up a secure umask - we'll set the proper one after login */ vsf_sysutil_set_umask(VSFTP_SECURE_UMASK); /* Fire up libc's timezone initialisation, before we chroot()! */ vsf_sysutil_tzset(); /* Signals. We'll always take -EPIPE rather than a rude signal, thanks */ vsf_sysutil_install_null_sighandler(kVSFSysUtilSigPIPE); } static void limits_init(void) { unsigned long limit = VSFTP_AS_LIMIT; if (tunable_text_userdb_names) { /* Turns out, LDAP lookups for lots of userid -> name mappings can really * bloat memory usage. */ limit *= 3; } vsf_sysutil_set_address_space_limit(limit); } static void session_init(struct vsf_session* p_sess) { /* Get the addresses of the control connection */ vsf_sysutil_getpeername(VSFTP_COMMAND_FD, &p_sess->p_remote_addr); vsf_sysutil_getsockname(VSFTP_COMMAND_FD, &p_sess->p_local_addr); /* If anonymous mode is active, fetch the uid of the anonymous user */ if (tunable_anonymous_enable) { const struct vsf_sysutil_user* p_user = 0; if (tunable_ftp_username) { p_user = vsf_sysutil_getpwnam(tunable_ftp_username); } if (p_user == 0) { die2("vsftpd: cannot locate user specified in 'ftp_username':", tunable_ftp_username); } p_sess->anon_ftp_uid = vsf_sysutil_user_getuid(p_user); } if (tunable_guest_enable) { const struct vsf_sysutil_user* p_user = 0; if (tunable_guest_username) { p_user = vsf_sysutil_getpwnam(tunable_guest_username); } if (p_user == 0) { die2("vsftpd: cannot locate user specified in 'guest_username':", tunable_guest_username); } p_sess->guest_user_uid = vsf_sysutil_user_getuid(p_user); } if (tunable_chown_uploads) { const struct vsf_sysutil_user* p_user = 0; if (tunable_chown_username) { p_user = vsf_sysutil_getpwnam(tunable_chown_username); } if (p_user == 0) { die2("vsftpd: cannot locate user specified in 'chown_username':", tunable_chown_username); } p_sess->anon_upload_chown_uid = vsf_sysutil_user_getuid(p_user); } } vsftpd-3.0.3/xinetd.d/0000755000175000017500000000000010750743700013545 5ustar chrischrisvsftpd-3.0.3/xinetd.d/vsftpd0000644000175000017500000000110610750743700014774 0ustar chrischris# default: on # description: # The vsftpd FTP server serves FTP connections. It uses # normal, unencrypted usernames and passwords for authentication. # vsftpd is designed to be secure. service ftp { socket_type = stream wait = no user = root server = /usr/local/sbin/vsftpd # server_args = # log_on_success += DURATION USERID # log_on_failure += USERID nice = 10 disable = no } vsftpd-3.0.3/access.c0000644000175000017500000000245511526577705013457 0ustar chrischris/* * Part of Very Secure FTPd * Licence: GPL v2 * Author: Chris Evans * access.c * * Routines to do very very simple access control based on filenames. */ #include "access.h" #include "ls.h" #include "tunables.h" #include "str.h" int vsf_access_check_file(const struct mystr* p_filename_str) { static struct mystr s_access_str; unsigned int iters = 0; if (!tunable_deny_file) { return 1; } if (str_isempty(&s_access_str)) { str_alloc_text(&s_access_str, tunable_deny_file); } if (vsf_filename_passes_filter(p_filename_str, &s_access_str, &iters)) { return 0; } else { struct str_locate_result loc_res = str_locate_str(p_filename_str, &s_access_str); if (loc_res.found) { return 0; } } return 1; } int vsf_access_check_file_visible(const struct mystr* p_filename_str) { static struct mystr s_access_str; unsigned int iters = 0; if (!tunable_hide_file) { return 1; } if (str_isempty(&s_access_str)) { str_alloc_text(&s_access_str, tunable_hide_file); } if (vsf_filename_passes_filter(p_filename_str, &s_access_str, &iters)) { return 0; } else { struct str_locate_result loc_res = str_locate_str(p_filename_str, &s_access_str); if (loc_res.found) { return 0; } } return 1; } vsftpd-3.0.3/ls.c0000644000175000017500000003246112025273663012623 0ustar chrischris/* * Part of Very Secure FTPd * Licence: GPL v2 * Author: Chris Evans * ls.c * * Would you believe, code to handle directory listing. */ #include "ls.h" #include "access.h" #include "defs.h" #include "str.h" #include "strlist.h" #include "sysstr.h" #include "sysutil.h" #include "tunables.h" static void build_dir_line(struct mystr* p_str, const struct mystr* p_filename_str, const struct vsf_sysutil_statbuf* p_stat, long curr_time); void vsf_ls_populate_dir_list(struct mystr_list* p_list, struct mystr_list* p_subdir_list, struct vsf_sysutil_dir* p_dir, const struct mystr* p_base_dir_str, const struct mystr* p_option_str, const struct mystr* p_filter_str, int is_verbose) { struct mystr dirline_str = INIT_MYSTR; struct mystr normalised_base_dir_str = INIT_MYSTR; struct str_locate_result loc_result; int a_option; int r_option; int t_option; int F_option; int do_stat = 0; long curr_time = 0; loc_result = str_locate_char(p_option_str, 'a'); a_option = loc_result.found; loc_result = str_locate_char(p_option_str, 'r'); r_option = loc_result.found; loc_result = str_locate_char(p_option_str, 't'); t_option = loc_result.found; loc_result = str_locate_char(p_option_str, 'F'); F_option = loc_result.found; loc_result = str_locate_char(p_option_str, 'l'); if (loc_result.found) { is_verbose = 1; } /* Invert "reverse" arg for "-t", the time sorting */ if (t_option) { r_option = !r_option; } if (is_verbose || t_option || F_option || p_subdir_list != 0) { do_stat = 1; } /* If the filter starts with a . then implicitly enable -a */ if (!str_isempty(p_filter_str) && str_get_char_at(p_filter_str, 0) == '.') { a_option = 1; } /* "Normalise" the incoming base directory string by making sure it * ends in a '/' if it is nonempty */ if (!str_equal_text(p_base_dir_str, ".")) { str_copy(&normalised_base_dir_str, p_base_dir_str); } if (!str_isempty(&normalised_base_dir_str)) { unsigned int len = str_getlen(&normalised_base_dir_str); if (str_get_char_at(&normalised_base_dir_str, len - 1) != '/') { str_append_char(&normalised_base_dir_str, '/'); } } /* If we're going to need to do time comparisions, cache the local time */ if (is_verbose) { curr_time = vsf_sysutil_get_time_sec(); } while (1) { static struct mystr s_next_filename_str; static struct mystr s_next_path_and_filename_str; static struct vsf_sysutil_statbuf* s_p_statbuf; str_next_dirent(&s_next_filename_str, p_dir); if (str_isempty(&s_next_filename_str)) { break; } { unsigned int len = str_getlen(&s_next_filename_str); if (len > 0 && str_get_char_at(&s_next_filename_str, 0) == '.') { if (!a_option && !tunable_force_dot_files) { continue; } if (!a_option && ((len == 2 && str_get_char_at(&s_next_filename_str, 1) == '.') || len == 1)) { continue; } } } /* Don't show hidden directory entries */ if (!vsf_access_check_file_visible(&s_next_filename_str)) { continue; } /* If we have an ls option which is a filter, apply it */ if (!str_isempty(p_filter_str)) { unsigned int iters = 0; if (!vsf_filename_passes_filter(&s_next_filename_str, p_filter_str, &iters)) { continue; } } /* Calculate the full path (relative to CWD) for lstat() and * output purposes */ str_copy(&s_next_path_and_filename_str, &normalised_base_dir_str); str_append_str(&s_next_path_and_filename_str, &s_next_filename_str); if (do_stat) { /* lstat() the file. Of course there's a race condition - the * directory entry may have gone away whilst we read it, so * ignore failure to stat */ int retval = str_lstat(&s_next_path_and_filename_str, &s_p_statbuf); if (vsf_sysutil_retval_is_error(retval)) { continue; } } if (is_verbose) { static struct mystr s_final_file_str; /* If it's a damn symlink, we need to append the target */ str_copy(&s_final_file_str, &s_next_filename_str); if (vsf_sysutil_statbuf_is_symlink(s_p_statbuf)) { static struct mystr s_temp_str; int retval = str_readlink(&s_temp_str, &s_next_path_and_filename_str); if (retval == 0 && !str_isempty(&s_temp_str)) { str_append_text(&s_final_file_str, " -> "); str_append_str(&s_final_file_str, &s_temp_str); } } if (F_option && vsf_sysutil_statbuf_is_dir(s_p_statbuf)) { str_append_char(&s_final_file_str, '/'); } build_dir_line(&dirline_str, &s_final_file_str, s_p_statbuf, curr_time); } else { /* Just emit the filenames - note, we prepend the directory for NLST * but not for LIST */ str_copy(&dirline_str, &s_next_path_and_filename_str); if (F_option) { if (vsf_sysutil_statbuf_is_dir(s_p_statbuf)) { str_append_char(&dirline_str, '/'); } else if (vsf_sysutil_statbuf_is_symlink(s_p_statbuf)) { str_append_char(&dirline_str, '@'); } } str_append_text(&dirline_str, "\r\n"); } /* Add filename into our sorted list - sorting by filename or time. Also, * if we are required to, maintain a distinct list of direct * subdirectories. */ { static struct mystr s_temp_str; const struct mystr* p_sort_str = 0; const struct mystr* p_sort_subdir_str = 0; if (!t_option) { p_sort_str = &s_next_filename_str; } else { str_alloc_text(&s_temp_str, vsf_sysutil_statbuf_get_sortkey_mtime(s_p_statbuf)); p_sort_str = &s_temp_str; p_sort_subdir_str = &s_temp_str; } str_list_add(p_list, &dirline_str, p_sort_str); if (p_subdir_list != 0 && vsf_sysutil_statbuf_is_dir(s_p_statbuf)) { str_list_add(p_subdir_list, &s_next_filename_str, p_sort_subdir_str); } } } /* END: while(1) */ str_list_sort(p_list, r_option); if (p_subdir_list != 0) { str_list_sort(p_subdir_list, r_option); } str_free(&dirline_str); str_free(&normalised_base_dir_str); } int vsf_filename_passes_filter(const struct mystr* p_filename_str, const struct mystr* p_filter_str, unsigned int* iters) { /* A simple routine to match a filename against a pattern. * This routine is used instead of e.g. fnmatch(3), because we should be * reluctant to trust the latter. fnmatch(3) involves _lots_ of string * parsing and handling. There is broad potential for any given fnmatch(3) * implementation to be buggy. * * Currently supported pattern(s): * - any number of wildcards, "*" or "?" * - {,} syntax (not nested) * * Note that pattern matching is only supported within the last path * component. For example, searching for /a/b/? will work, but searching * for /a/?/c will not. */ struct mystr filter_remain_str = INIT_MYSTR; struct mystr name_remain_str = INIT_MYSTR; struct mystr temp_str = INIT_MYSTR; struct mystr brace_list_str = INIT_MYSTR; struct mystr new_filter_str = INIT_MYSTR; int ret = 0; char last_token = 0; int must_match_at_current_pos = 1; str_copy(&filter_remain_str, p_filter_str); str_copy(&name_remain_str, p_filename_str); while (!str_isempty(&filter_remain_str) && *iters < VSFTP_MATCHITERS_MAX) { static struct mystr s_match_needed_str; /* Locate next special token */ struct str_locate_result locate_result = str_locate_chars(&filter_remain_str, "*?{"); (*iters)++; /* Isolate text leading up to token (if any) - needs to be matched */ if (locate_result.found) { unsigned int indexx = locate_result.index; str_left(&filter_remain_str, &s_match_needed_str, indexx); str_mid_to_end(&filter_remain_str, &temp_str, indexx + 1); str_copy(&filter_remain_str, &temp_str); last_token = locate_result.char_found; } else { /* No more tokens. Must match remaining filter string exactly. */ str_copy(&s_match_needed_str, &filter_remain_str); str_empty(&filter_remain_str); last_token = 0; } if (!str_isempty(&s_match_needed_str)) { /* Need to match something.. could be a match which has to start at * current position, or we could allow it to start anywhere */ unsigned int indexx; locate_result = str_locate_str(&name_remain_str, &s_match_needed_str); if (!locate_result.found) { /* Fail */ goto out; } indexx = locate_result.index; if (must_match_at_current_pos && indexx > 0) { goto out; } /* Chop matched string out of remainder */ str_mid_to_end(&name_remain_str, &temp_str, indexx + str_getlen(&s_match_needed_str)); str_copy(&name_remain_str, &temp_str); } if (last_token == '?') { if (str_isempty(&name_remain_str)) { goto out; } str_right(&name_remain_str, &temp_str, str_getlen(&name_remain_str) - 1); str_copy(&name_remain_str, &temp_str); must_match_at_current_pos = 1; } else if (last_token == '{') { struct str_locate_result end_brace = str_locate_char(&filter_remain_str, '}'); must_match_at_current_pos = 1; if (end_brace.found) { str_split_char(&filter_remain_str, &temp_str, '}'); str_copy(&brace_list_str, &filter_remain_str); str_copy(&filter_remain_str, &temp_str); str_split_char(&brace_list_str, &temp_str, ','); while (!str_isempty(&brace_list_str)) { str_copy(&new_filter_str, &brace_list_str); str_append_str(&new_filter_str, &filter_remain_str); if (vsf_filename_passes_filter(&name_remain_str, &new_filter_str, iters)) { ret = 1; goto out; } str_copy(&brace_list_str, &temp_str); str_split_char(&brace_list_str, &temp_str, ','); } goto out; } else if (str_isempty(&name_remain_str) || str_get_char_at(&name_remain_str, 0) != '{') { goto out; } else { str_right(&name_remain_str, &temp_str, str_getlen(&name_remain_str) - 1); str_copy(&name_remain_str, &temp_str); } } else { must_match_at_current_pos = 0; } } /* Any incoming string left means no match unless we ended on the correct * type of wildcard. */ if (str_getlen(&name_remain_str) > 0 && last_token != '*') { goto out; } /* OK, a match */ ret = 1; if (*iters == VSFTP_MATCHITERS_MAX) { ret = 0; } out: str_free(&filter_remain_str); str_free(&name_remain_str); str_free(&temp_str); str_free(&brace_list_str); str_free(&new_filter_str); return ret; } static void build_dir_line(struct mystr* p_str, const struct mystr* p_filename_str, const struct vsf_sysutil_statbuf* p_stat, long curr_time) { static struct mystr s_tmp_str; filesize_t size = vsf_sysutil_statbuf_get_size(p_stat); /* Permissions */ str_alloc_text(p_str, vsf_sysutil_statbuf_get_perms(p_stat)); str_append_char(p_str, ' '); /* Hard link count */ str_alloc_ulong(&s_tmp_str, vsf_sysutil_statbuf_get_links(p_stat)); str_lpad(&s_tmp_str, 4); str_append_str(p_str, &s_tmp_str); str_append_char(p_str, ' '); /* User */ if (tunable_hide_ids) { str_alloc_text(&s_tmp_str, "ftp"); } else { int uid = vsf_sysutil_statbuf_get_uid(p_stat); struct vsf_sysutil_user* p_user = 0; if (tunable_text_userdb_names) { p_user = vsf_sysutil_getpwuid(uid); } if (p_user == 0) { str_alloc_ulong(&s_tmp_str, (unsigned long) uid); } else { str_alloc_text(&s_tmp_str, vsf_sysutil_user_getname(p_user)); } } str_rpad(&s_tmp_str, 8); str_append_str(p_str, &s_tmp_str); str_append_char(p_str, ' '); /* Group */ if (tunable_hide_ids) { str_alloc_text(&s_tmp_str, "ftp"); } else { int gid = vsf_sysutil_statbuf_get_gid(p_stat); struct vsf_sysutil_group* p_group = 0; if (tunable_text_userdb_names) { p_group = vsf_sysutil_getgrgid(gid); } if (p_group == 0) { str_alloc_ulong(&s_tmp_str, (unsigned long) gid); } else { str_alloc_text(&s_tmp_str, vsf_sysutil_group_getname(p_group)); } } str_rpad(&s_tmp_str, 8); str_append_str(p_str, &s_tmp_str); str_append_char(p_str, ' '); /* Size in bytes */ str_alloc_filesize_t(&s_tmp_str, size); str_lpad(&s_tmp_str, 8); str_append_str(p_str, &s_tmp_str); str_append_char(p_str, ' '); /* Date stamp */ str_append_text(p_str, vsf_sysutil_statbuf_get_date(p_stat, tunable_use_localtime, curr_time)); str_append_char(p_str, ' '); /* Filename */ str_append_str(p_str, p_filename_str); str_append_text(p_str, "\r\n"); } vsftpd-3.0.3/prelogin.h0000644000175000017500000000064310750743701014024 0ustar chrischris#ifndef VSF_PRELOGIN_H #define VSF_PRELOGIN_H struct vsf_session; /* init_connection() * PURPOSE * Called as an entry point to FTP protocol processing, when a client connects. * This function will emit the FTP greeting, then start talking FTP protocol * to the client. * PARAMETERS * p_sess - the current session object */ void init_connection(struct vsf_session* p_sess); #endif /* VSF_PRELOGIN_H */ vsftpd-3.0.3/SECURITY/0000755000175000017500000000000011230162551013270 5ustar chrischrisvsftpd-3.0.3/SECURITY/IMPLEMENTATION0000644000175000017500000000433610750743701015356 0ustar chrischrisThis document details a few steps and decisions taken to ensure vsftpd is free of common implementation flaws. Tackling the buffer overflow ============================ Probably the most common implementation flaw causing security problems is the buffer overflow. Buffer overflows come in many shapes and sizes - overflows onto the stack, overflows off the end of dynamically malloc()'ed areas, overflows into static data areas. They range from easy to spot (where a user can put an arbitrary length string into a fixed size buffer), to very difficult to spot - buffer size miscalculations or single byte overflows. Or convoluted code where the buffer's definition and various usages are far apart. The problem is that people insist on replicating buffer size handling code and buffer size security checks many times (or, of course, they omit size checks altogther). It is little surprise, then, that sometimes errors creep in to the checks. The correct solution is to hide the buffer handling code behind an API. All buffer allocating, copying, size calculations, extending, etc. are done by a single piece of generic code. The size security checks need to be written once. You can concentrate on getting this one instance of code correct. From the client's point of view, they are no longer dealing with a buffer. The buffer is encapsulated within the buffer API. All modifications to the buffer safely go through the API. If this sounds familiar, it is because what vsftpd implements is very similar to a C++ string class. You can do OO programming in C too, you know ;-) A key point of having the buffer API in place is that it is MORE DIFFICULT to abuse the API than it is to use it properly. Try and create a buffer memory corruption or overflow scenario using just the buffer API. Unfortunately, secure string/buffer usage through a common API has not caught on much, despite the benefits it brings. Is it under publicised as a solution? Or do people have too much sentimental attachment to strcpy(), strlen(), malloc(), strcat() etc? Of notable exception, it is my understanding that at least the rather secure qmail program uses secure buffer handling, and I'd expect that to extend to all Dan Bernstein software. (Let me know of other good examples). vsftpd-3.0.3/SECURITY/OVERVIEW0000644000175000017500000000074610750743701014500 0ustar chrischrisThe documents in this directory contain information about the security of vsftpd. They explain why various aspects of vsftpd were coded the way they are. File Contents DESIGN Comments on the overall architecture of vsftpd, from a security standpoint. IMPLEMENTATION Comments on steps taken to ensure a secure implementation. TRUST Comments on external components trusted or distrusted by vsftpd. vsftpd-3.0.3/SECURITY/DESIGN0000644000175000017500000001606611230162550014174 0ustar chrischrisThis document explains the design goals and decisions behind vsftpd. The importance of a secure design ================================= In a world full of good, careful coders who do not make mistakes, a secure design would not be necessary. After all, in the absence of any programming errors, security would not differ no matter how the program is arranged. Unfortunately, this is not an ideal world, and coders make plenty of mistakes. Even the careful coders make mistakes. Code auditing is important, and goes some way towards eliminating coding mistakes after the fact. However, we have no guarantee that an audit will catch all the flaws. So, a secure design acknowledges the possibility of undiscovered flaws, and takes steps to minimise the security impact these flaws can have. An obvious example of something we want to do is to apply the principle of least privilege, which ensure that every part of the program runs with the privilege it needs and no more. An example of insecure design ============================= Examples of insecure design may be found in most other ftpd's. That's one of the reasons vsftpd has been written. We'll pick on wu-ftpd as a specific example, since it is rumoured to run about half of all ftp services. If I log on to wu-ftpd as an anonymous user, a process is run on my behalf to serve my ftp session. Unfortunately, this process typically runs with full root privileges on the remote machine. This means that any security flaw present in parsing the copious ftp protocol will lead to full compromise of that machine. Two concrete examples are the recent wu-ftpd format string bug (June 1999), and a buffer overflow dealing with large paths a few months beforehand. Even OpenBSD's ftpd-BSD had a format string bug leading to remote root compromise of the affected machine, illustrating an earlier point about the requirement for secure design even in the presence of heavy auditing. Secure design under UNIX ======================== vsftpd is written to run under UNIX-like operating systems, and so its secure design is constrained by the facilities offered by UNIX. Ideally, UNIX would have a proper security model which would offer fine grained access control to all system interactions (files, network, etc). It doesn't, but it does offer some useful and often overlooked facilities which help us to implement the principle of least privilege: - Strong inter-process communication facilities In UNIX, the process is a strongly defined boundary. Different privilege credentials may be assigned to different processes, which are not able to interfere with each other. This is a very basic facility of UNIX. It makes sense to use this facility to totally separate parts of a program which do not need to be privileged (most) from those parts that do (typically minimal). The privileged and unprivileged parts of the program then communicate via one of many UNIX IPC mechanisms - perhaps a socketpair or IPC (the former is attractive because UNIX lets you pass file handles over a socket). The minimal privileged process exercises the "principle of distrust" - it carefully filters what the unprivileged process asks it to do, so that even if the unprivileged process is compromised, it cannot ask the privileged process to do anything we don't want to allow. - chroot() chroot() is an often overlooked but useful tool. It can be used very effectively as a damage limitation tool. Imagine a remotely compromised process which does not run as root, but also does not use chroot(). Now look at what the attacker can do. Amongst the worst items are pilfering of all publicly readable files, and also attempting to execute any publicly executable suid-root programs to try and elevate privilege. Now imagine the same compromised process with a chroot() to an empty directory. The attackers options to do unpleasant things are substantially diminished. No, chroot() is not the ideal way to do what we have just accomplished, but it is what we have got to work with. In an ideal environment with fine grained security, we would default to having access to _no_ files at all, and deliberately not ask for access to any. - Capabilities (Linux 2.2+) Like chroot(), capabilities are essentially a damage limitation excercise. They are also much less widespread than the other UNIX facilities detailled above. Nonetheless, they warrant mentioning because Linux has them, and they are used in vsftpd because that is the primary devlopment platform. Capabilities split up the all powerful root privilege into lots of sometimes orthogonal privileges. Some of the capabilities represent privileges which are often the basis for requiring a program to run with full root privileges. Examples include CAP_NET_RAW (ping, traceroute) and CAP_NET_BIND_SERVICE (rlogin). By using capabilities to ensure we only have the privilege we need (within the somewhat disappointing granularity they offer), we again limit the potential damage of security holes. Presenting vsftpd's secure design ================================= vsftpd employs a secure design. The UNIX facilities outlined above are used to good effect. The design decisions taken are as follows: 1) All parsing and acting on potentially malicious remote network data is done in a process running as an unprivileged user. Furthermore, this process runs in a chroot() jail, ensuring only the ftp files area is accessible. 2) Any privileged operations are handled in a privileged parent process. The code for this privileged parent process is as small as possible for safety. 3) This same privileged parent process receives requests from the unprivileged child over a socket. All requests are distrusted. Here are example requests: - Login request. The child sends username and password. Only if the details are correct does the privileged parent launch a new child with the appropriate user credentials. - chown() request. The child may request a recently uploaded file gets chown'ed() to root for security purposes. The parent is careful to only allow chown() to root, and only from files owned by the ftp user. - Get privileged socket request. The ftp protocol says we are supposed to emit data connections from port 20. This requires privilege. The privileged parent process creates the privileged socket and passes it to child over the socket. 4) This same privileged parent process makes use of capabilities and chroot(), to run with the least privilege required. After login, depending on what options have been selected, the privileged parent dynamically calculates what privileges it requires. In some cases, this amounts to no privilege, and the privileged parent just exits, leaving no part of vsftpd running with privilege. 5) vsftpd-2.0.0 introduces SSL / TLS support using OpenSSL. ALL SSL protocol parsing is performed in a chroot() jail, running under an unprivileged user. This means both pre-authenticated and post-authenticated SSL protocol parsing; it's actually quite hard to do, but vsftpd manages it in the name of being secure. I'm unaware of any other FTP server which supports both SSL / TLS and privilege separation, and gets this right. Comments on this document are welcomed. vsftpd-3.0.3/SECURITY/TRUST0000644000175000017500000001265210750743701014152 0ustar chrischrisThis document describes what the vsftpd code trusts, what it doesn't trust, and the reasoning behind any trust decisions. The importance of trust and trust relationships =============================================== Imagine a largely well written and secure piece of code. Now imagine that this piece of code delegates a task to an external program, perhaps in the name of code reuse. Now, if this external program is sloppily coded and insecure, we've wasted a lot of effort making our original program secure; our erroneous trust of the buggy external program means we have a security leak, even though we were careful in _our_ code. There is a very similar situation with buggy library APIs. Imagine our secure program calling some complex library function which lets the side down by containing a security hole. Lets put some concrete examples on the two similar above considerations. We can even give examples in the context of FTP daemons. 1) External /bin/ls helper A very common operation asked of FTP servers is to provide a directory listing. Unfortunately, convention seems to be to emit the directory listing in UNIX "/bin/ls -l" format. Even the Microsoft FTP service can be observed to do this. When writing an FTP server for the UNIX platform, then, this leads to the temptation to reuse /bin/ls as a child process, to avoid having to rewrite a load of code to handle directory listings. Even more unfortunately, FTP server writers seem to want to adopt the versatility of the average /bin/ls implementation. This means they allow clients to specify arbitrary parameters to /bin/ls. By using an external /bin/ls command, we would tie the security of our FTP server to that of the /bin/ls code. Be careful not to underestimate the amount of code paths in /bin/ls which are explorable by a remote malicious user. GNU /bin/ls has a myriad of options. Some of these options are complex such as -I or the various formatting options. All it takes is a single coding flaw in the handling of one of these options, and your FTP security is in trouble. By using an external /bin/ls, you also inherit the risk of any dangerous or complex APIs it uses. For example, calls to libc's complex fnmatch() or glob() functions, which will get given arbitrary malicious user controlled data as the search patterns. Also remember that users (and sometimes remote users) can upload/create files, and filenames are a very prominent input to /bin/ls. To conclude: vsftpd has no intention of using an external /bin/ls program because of the risks outlined above. Even if I were to audit e.g. GNU fileutils /bin/ls, and also important parts of glibc, this would still leave security in an unknown state on other platforms. The solution I have employed is to write a minimal internal implementation of a /bin/ls listing generator; it's hardly difficult. As a happy side effect, this will boost performance by avoiding unneccesary fork()s and exec()s! Here's some quick data about FTP servers which tend to use external ls programs: ftp.wuftpd.org: ftp> ls --version 227 Entering Passive Mode (x.x.x.x.x.x) 150 Opening ASCII mode data connection for /bin/ls. ls (GNU fileutils) 3.16 226 Transfer complete. ftp.digital.com: ftp> ls -v 227 Entering Passive Mode (x.x.x.x.x.x) 150 Opening ASCII mode data connection for /bin/ls. /bin/ls: illegal option -- v usage: ls [ -1ACFLRabcdfgilmnopqrstux ] [files] 226 Transfer complete. Note that /bin/ls is not the only external program invoked by common FTP servers such as wu-ftpd. wu-ftpd also has the ability to invoke "tar" and "gzip" on the fly, so there are trust relationships there too. 2) Complex library APIs vsftpd is very careful to avoid using library calls which are potentially dangerous. I would typically classify calls as dangerous if they interact with the network non-trivially, or take malicious user supplied data and start parsing it in a major way. Some examples are clearly required (vsftpd avoids using any of the following): 1) fnmatch(). This is the libc glob pattern matcher. The danger comes from the fact that the user supplies the glob pattern - "ls *.mp3" would be a simple example. Furthermore, glob pattern matching is complex and involves a lot of string handling. 2) gethostbyaddr(). This is a libc call to resolve an IP address to a hostname. Unfortunately, doing this is quite complicated. When you call gethostbyaddr(), a lot of work goes on under the covers. This usually involves making a network call out to the DNS server, and, dangerously, parsing the response. For clarity (and clarity is a very important part of security), all external APIs used by vsftpd are encapsulated within two "system interaction" files, named "sysutil.c", and "sysdeputil.c" (for the more variable/system dependent calls). This provides a convenient audit point for ascertaining which calls vsftpd trusts. vsftpd-2.0.0 introduces SSL / TLS support using OpenSSL. OpenSSL is a massive quantity of code which is essentially parsing complex protocol under the full control of remote malicious clients. SSL / TLS is disabled by default, both at compile time and run time. This forces packagers and administrators to make the decision that they trust the OpenSSL library. I personally haven't yet formed an opinion on whether I consider the OpenSSL code trustworthy. Summary ======= Be very aware of what APIs and/or programs you are trusting, or you might end up creating a trust relationship which makes your program exploitable -- through no direct fault of your own. vsftpd-3.0.3/ftpcodes.h0000644000175000017500000000453010754164266014022 0ustar chrischris#ifndef VSF_FTPCODES_H #define VSF_FTPCODES_H #define FTP_DATACONN 150 #define FTP_NOOPOK 200 #define FTP_TYPEOK 200 #define FTP_PORTOK 200 #define FTP_EPRTOK 200 #define FTP_UMASKOK 200 #define FTP_CHMODOK 200 #define FTP_EPSVALLOK 200 #define FTP_STRUOK 200 #define FTP_MODEOK 200 #define FTP_PBSZOK 200 #define FTP_PROTOK 200 #define FTP_OPTSOK 200 #define FTP_ALLOOK 202 #define FTP_FEAT 211 #define FTP_STATOK 211 #define FTP_SIZEOK 213 #define FTP_MDTMOK 213 #define FTP_STATFILE_OK 213 #define FTP_SITEHELP 214 #define FTP_HELP 214 #define FTP_SYSTOK 215 #define FTP_GREET 220 #define FTP_GOODBYE 221 #define FTP_ABOR_NOCONN 225 #define FTP_TRANSFEROK 226 #define FTP_ABOROK 226 #define FTP_PASVOK 227 #define FTP_EPSVOK 229 #define FTP_LOGINOK 230 #define FTP_AUTHOK 234 #define FTP_CWDOK 250 #define FTP_RMDIROK 250 #define FTP_DELEOK 250 #define FTP_RENAMEOK 250 #define FTP_PWDOK 257 #define FTP_MKDIROK 257 #define FTP_GIVEPWORD 331 #define FTP_RESTOK 350 #define FTP_RNFROK 350 #define FTP_IDLE_TIMEOUT 421 #define FTP_DATA_TIMEOUT 421 #define FTP_TOO_MANY_USERS 421 #define FTP_IP_LIMIT 421 #define FTP_IP_DENY 421 #define FTP_TLS_FAIL 421 #define FTP_BADSENDCONN 425 #define FTP_BADSENDNET 426 #define FTP_BADSENDFILE 451 #define FTP_BADCMD 500 #define FTP_BADOPTS 501 #define FTP_COMMANDNOTIMPL 502 #define FTP_NEEDUSER 503 #define FTP_NEEDRNFR 503 #define FTP_BADPBSZ 503 #define FTP_BADPROT 503 #define FTP_BADSTRU 504 #define FTP_BADMODE 504 #define FTP_BADAUTH 504 #define FTP_NOSUCHPROT 504 #define FTP_NEEDENCRYPT 522 #define FTP_EPSVBAD 522 #define FTP_DATATLSBAD 522 #define FTP_LOGINERR 530 #define FTP_NOHANDLEPROT 536 #define FTP_FILEFAIL 550 #define FTP_NOPERM 550 #define FTP_UPLOADFAIL 553 #endif /* VSF_FTPCODES_H */ vsftpd-3.0.3/sysstr.c0000644000175000017500000000737311672074737013570 0ustar chrischris/* * Part of Very Secure FTPd * Licence: GPL v2 * Author: Chris Evans * sysstr.c * * This file basically wraps system functions so that we can deal in our * nice abstracted string buffer objects. */ #include "sysstr.h" #include "str.h" #include "secbuf.h" #include "sysutil.h" #include "defs.h" #include "utility.h" #include "tunables.h" void str_getcwd(struct mystr* p_str) { static char* p_getcwd_buf; char* p_ret; if (p_getcwd_buf == 0) { vsf_secbuf_alloc(&p_getcwd_buf, VSFTP_PATH_MAX); } /* In case getcwd() fails */ str_empty(p_str); p_ret = vsf_sysutil_getcwd(p_getcwd_buf, VSFTP_PATH_MAX); if (p_ret != 0) { str_alloc_text(p_str, p_getcwd_buf); } } int str_write_loop(const struct mystr* p_str, const int fd) { return vsf_sysutil_write_loop(fd, str_getbuf(p_str), str_getlen(p_str)); } int str_read_loop(struct mystr* p_str, const int fd) { return vsf_sysutil_read_loop( fd, (char*) str_getbuf(p_str), str_getlen(p_str)); } int str_mkdir(const struct mystr* p_str, const unsigned int mode) { return vsf_sysutil_mkdir(str_getbuf(p_str), mode); } int str_rmdir(const struct mystr* p_str) { return vsf_sysutil_rmdir(str_getbuf(p_str)); } int str_unlink(const struct mystr* p_str) { return vsf_sysutil_unlink(str_getbuf(p_str)); } int str_chdir(const struct mystr* p_str) { return vsf_sysutil_chdir(str_getbuf(p_str)); } int str_open(const struct mystr* p_str, const enum EVSFSysStrOpenMode mode) { enum EVSFSysUtilOpenMode open_mode = kVSFSysStrOpenUnknown; switch (mode) { case kVSFSysStrOpenReadOnly: open_mode = kVSFSysUtilOpenReadOnly; break; case kVSFSysStrOpenUnknown: /* Fall through */ default: bug("unknown mode value in str_open"); break; } return vsf_sysutil_open_file(str_getbuf(p_str), open_mode); } int str_stat(const struct mystr* p_str, struct vsf_sysutil_statbuf** p_ptr) { return vsf_sysutil_stat(str_getbuf(p_str), p_ptr); } int str_lstat(const struct mystr* p_str, struct vsf_sysutil_statbuf** p_ptr) { return vsf_sysutil_lstat(str_getbuf(p_str), p_ptr); } int str_create_exclusive(const struct mystr* p_str) { return vsf_sysutil_create_file_exclusive(str_getbuf(p_str)); } int str_create(const struct mystr* p_str) { return vsf_sysutil_create_or_open_file( str_getbuf(p_str), tunable_file_open_mode); } int str_chmod(const struct mystr* p_str, unsigned int mode) { return vsf_sysutil_chmod(str_getbuf(p_str), mode); } int str_rename(const struct mystr* p_from_str, const struct mystr* p_to_str) { return vsf_sysutil_rename(str_getbuf(p_from_str), str_getbuf(p_to_str)); } struct vsf_sysutil_dir* str_opendir(const struct mystr* p_str) { return vsf_sysutil_opendir(str_getbuf(p_str)); } void str_next_dirent(struct mystr* p_filename_str, struct vsf_sysutil_dir* p_dir) { const char* p_filename = vsf_sysutil_next_dirent(p_dir); str_empty(p_filename_str); if (p_filename != 0) { str_alloc_text(p_filename_str, p_filename); } } int str_readlink(struct mystr* p_str, const struct mystr* p_filename_str) { static char* p_readlink_buf; int retval; if (p_readlink_buf == 0) { vsf_secbuf_alloc(&p_readlink_buf, VSFTP_PATH_MAX); } /* In case readlink() fails */ str_empty(p_str); /* Note: readlink(2) does not NULL terminate, but our wrapper does */ retval = vsf_sysutil_readlink(str_getbuf(p_filename_str), p_readlink_buf, VSFTP_PATH_MAX); if (vsf_sysutil_retval_is_error(retval)) { return retval; } str_alloc_text(p_str, p_readlink_buf); return 0; } struct vsf_sysutil_user* str_getpwnam(const struct mystr* p_user_str) { return vsf_sysutil_getpwnam(str_getbuf(p_user_str)); } void str_syslog(const struct mystr* p_str, int severe) { vsf_sysutil_syslog(str_getbuf(p_str), severe); } vsftpd-3.0.3/hash.c0000644000175000017500000000641112025277463013126 0ustar chrischris/* * Part of Very Secure FTPd * Licence: GPL v2 * Author: Chris Evans * hash.c * * Routines to handle simple hash table lookups and modifications. */ #include "hash.h" #include "sysutil.h" #include "utility.h" struct hash_node { void* p_key; void* p_value; struct hash_node* p_prev; struct hash_node* p_next; }; struct hash { unsigned int buckets; unsigned int key_size; unsigned int value_size; hashfunc_t hash_func; struct hash_node** p_nodes; }; /* Internal functions */ struct hash_node** hash_get_bucket(struct hash* p_hash, void* p_key); struct hash_node* hash_get_node_by_key(struct hash* p_hash, void* p_key); struct hash* hash_alloc(unsigned int buckets, unsigned int key_size, unsigned int value_size, hashfunc_t hash_func) { unsigned int size; struct hash* p_hash = vsf_sysutil_malloc(sizeof(*p_hash)); p_hash->buckets = buckets; p_hash->key_size = key_size; p_hash->value_size = value_size; p_hash->hash_func = hash_func; size = (unsigned int) sizeof(struct hash_node*) * buckets; p_hash->p_nodes = vsf_sysutil_malloc(size); vsf_sysutil_memclr(p_hash->p_nodes, size); return p_hash; } void* hash_lookup_entry(struct hash* p_hash, void* p_key) { struct hash_node* p_node = hash_get_node_by_key(p_hash, p_key); if (!p_node) { return p_node; } return p_node->p_value; } void hash_add_entry(struct hash* p_hash, void* p_key, void* p_value) { struct hash_node** p_bucket; struct hash_node* p_new_node; if (hash_lookup_entry(p_hash, p_key)) { bug("duplicate hash key"); } p_bucket = hash_get_bucket(p_hash, p_key); p_new_node = vsf_sysutil_malloc(sizeof(*p_new_node)); p_new_node->p_prev = 0; p_new_node->p_next = 0; p_new_node->p_key = vsf_sysutil_malloc(p_hash->key_size); vsf_sysutil_memcpy(p_new_node->p_key, p_key, p_hash->key_size); p_new_node->p_value = vsf_sysutil_malloc(p_hash->value_size); vsf_sysutil_memcpy(p_new_node->p_value, p_value, p_hash->value_size); if (!*p_bucket) { *p_bucket = p_new_node; } else { p_new_node->p_next = *p_bucket; (*p_bucket)->p_prev = p_new_node; *p_bucket = p_new_node; } } void hash_free_entry(struct hash* p_hash, void* p_key) { struct hash_node* p_node = hash_get_node_by_key(p_hash, p_key); if (!p_node) { bug("hash node not found"); } vsf_sysutil_free(p_node->p_key); vsf_sysutil_free(p_node->p_value); if (p_node->p_prev) { p_node->p_prev->p_next = p_node->p_next; } else { struct hash_node** p_bucket = hash_get_bucket(p_hash, p_key); *p_bucket = p_node->p_next; } if (p_node->p_next) { p_node->p_next->p_prev = p_node->p_prev; } vsf_sysutil_free(p_node); } struct hash_node** hash_get_bucket(struct hash* p_hash, void* p_key) { unsigned int bucket = (*p_hash->hash_func)(p_hash->buckets, p_key); if (bucket >= p_hash->buckets) { bug("bad bucket lookup"); } return &(p_hash->p_nodes[bucket]); } struct hash_node* hash_get_node_by_key(struct hash* p_hash, void* p_key) { struct hash_node** p_bucket = hash_get_bucket(p_hash, p_key); struct hash_node* p_node = *p_bucket; if (!p_node) { return p_node; } while (p_node != 0 && vsf_sysutil_memcmp(p_key, p_node->p_key, p_hash->key_size) != 0) { p_node = p_node->p_next; } return p_node; } vsftpd-3.0.3/privsock.c0000644000175000017500000000766011205336171014041 0ustar chrischris/* * Part of Very Secure FTPd * Licence: GPL v2 * Author: Chris Evans * privsock.c * * This file contains code for a simple message and file descriptor passing * API, over a pair of UNIX sockets. * The messages are typically travelling across a privilege boundary, with * heavy distrust of messages on the side of more privilege. */ #include "privsock.h" #include "utility.h" #include "defs.h" #include "str.h" #include "netstr.h" #include "sysutil.h" #include "sysdeputil.h" #include "session.h" void priv_sock_init(struct vsf_session* p_sess) { struct vsf_sysutil_socketpair_retval retval; if (p_sess->parent_fd != -1) { bug("parent_fd active"); } if (p_sess->child_fd != -1) { bug("child_fd active"); } retval = vsf_sysutil_unix_stream_socketpair(); p_sess->parent_fd = retval.socket_one; p_sess->child_fd = retval.socket_two; } void priv_sock_close(struct vsf_session* p_sess) { if (p_sess->parent_fd != -1) { vsf_sysutil_close(p_sess->parent_fd); p_sess->parent_fd = -1; } if (p_sess->child_fd != -1) { vsf_sysutil_close(p_sess->child_fd); p_sess->child_fd = -1; } } void priv_sock_set_parent_context(struct vsf_session* p_sess) { if (p_sess->child_fd == -1) { bug("child_fd not active"); } vsf_sysutil_close(p_sess->child_fd); p_sess->child_fd = -1; } void priv_sock_set_child_context(struct vsf_session* p_sess) { if (p_sess->parent_fd == -1) { bug("parent_fd not active"); } vsf_sysutil_close(p_sess->parent_fd); p_sess->parent_fd = -1; } void priv_sock_send_cmd(int fd, char cmd) { int retval = vsf_sysutil_write_loop(fd, &cmd, sizeof(cmd)); if (retval != sizeof(cmd)) { die("priv_sock_send_cmd"); } } void priv_sock_send_str(int fd, const struct mystr* p_str) { unsigned int len = str_getlen(p_str); priv_sock_send_int(fd, (int) len); if (len > 0) { str_netfd_write(p_str, fd); } } void priv_sock_send_buf(int fd, const char* p_buf, unsigned int len) { priv_sock_send_int(fd, (int) len); if (len > 0) { if (vsf_sysutil_write_loop(fd, p_buf, len) != (int) len) { die("priv_sock_send_buf"); } } } void priv_sock_recv_buf(int fd, char* p_buf, unsigned int len) { unsigned int recv_len = (unsigned int) priv_sock_get_int(fd); if (recv_len > len) { bug("recv_len bigger than buffer"); } if (recv_len > 0) { if (vsf_sysutil_read_loop(fd, p_buf, recv_len) != (int) recv_len) { die("priv_sock_recv_buf"); } } } char priv_sock_get_result(int fd) { char res; int retval = vsf_sysutil_read_loop(fd, &res, sizeof(res)); if (retval != sizeof(res)) { die("priv_sock_get_result"); } return res; } char priv_sock_get_cmd(int fd) { char res; int retval = vsf_sysutil_read_loop(fd, &res, sizeof(res)); if (retval != sizeof(res)) { die("priv_sock_get_cmd"); } return res; } void priv_sock_get_str(int fd, struct mystr* p_dest) { unsigned int len = (unsigned int) priv_sock_get_int(fd); if (len > VSFTP_PRIVSOCK_MAXSTR) { die("priv_sock_get_str: too big"); } str_empty(p_dest); if (len > 0) { int retval = str_netfd_read(p_dest, fd, len); if ((unsigned int) retval != len) { die("priv_sock_get_str: read error"); } } } void priv_sock_send_result(int fd, char res) { int retval = vsf_sysutil_write_loop(fd, &res, sizeof(res)); if (retval != sizeof(res)) { die("priv_sock_send_result"); } } void priv_sock_send_fd(int fd, int send_fd) { vsf_sysutil_send_fd(fd, send_fd); } int priv_sock_recv_fd(int fd) { return vsf_sysutil_recv_fd(fd); } void priv_sock_send_int(int fd, int the_int) { int retval = vsf_sysutil_write_loop(fd, &the_int, sizeof(the_int)); if (retval != sizeof(the_int)) { die("priv_sock_send_int"); } } int priv_sock_get_int(int fd) { int the_int; int retval = vsf_sysutil_read_loop(fd, &the_int, sizeof(the_int)); if (retval != sizeof(the_int)) { die("priv_sock_get_int"); } return the_int; } vsftpd-3.0.3/postlogin.c0000644000175000017500000016421512553764372014236 0ustar chrischris/* * Part of Very Secure FTPd * Licence: GPL v2 * Author: Chris Evans * postlogin.c */ #include "postlogin.h" #include "session.h" #include "oneprocess.h" #include "twoprocess.h" #include "ftpcodes.h" #include "ftpcmdio.h" #include "ftpdataio.h" #include "utility.h" #include "tunables.h" #include "defs.h" #include "str.h" #include "sysstr.h" #include "banner.h" #include "sysutil.h" #include "logging.h" #include "sysdeputil.h" #include "ipaddrparse.h" #include "access.h" #include "features.h" #include "ssl.h" #include "vsftpver.h" #include "opts.h" /* Private local functions */ static void handle_pwd(struct vsf_session* p_sess); static void handle_cwd(struct vsf_session* p_sess); static void handle_pasv(struct vsf_session* p_sess, int is_epsv); static void handle_retr(struct vsf_session* p_sess, int is_http); static void handle_cdup(struct vsf_session* p_sess); static void handle_list(struct vsf_session* p_sess); static void handle_type(struct vsf_session* p_sess); static void handle_port(struct vsf_session* p_sess); static void handle_stor(struct vsf_session* p_sess); static void handle_mkd(struct vsf_session* p_sess); static void handle_rmd(struct vsf_session* p_sess); static void handle_dele(struct vsf_session* p_sess); static void handle_rest(struct vsf_session* p_sess); static void handle_rnfr(struct vsf_session* p_sess); static void handle_rnto(struct vsf_session* p_sess); static void handle_nlst(struct vsf_session* p_sess); static void handle_size(struct vsf_session* p_sess); static void handle_site(struct vsf_session* p_sess); static void handle_appe(struct vsf_session* p_sess); static void handle_mdtm(struct vsf_session* p_sess); static void handle_site_chmod(struct vsf_session* p_sess, struct mystr* p_arg_str); static void handle_site_umask(struct vsf_session* p_sess, struct mystr* p_arg_str); static void handle_eprt(struct vsf_session* p_sess); static void handle_help(struct vsf_session* p_sess); static void handle_stou(struct vsf_session* p_sess); static void handle_stat(struct vsf_session* p_sess); static void handle_stat_file(struct vsf_session* p_sess); static void handle_logged_in_user(struct vsf_session* p_sess); static void handle_logged_in_pass(struct vsf_session* p_sess); static void handle_http(struct vsf_session* p_sess); static int pasv_active(struct vsf_session* p_sess); static int port_active(struct vsf_session* p_sess); static void pasv_cleanup(struct vsf_session* p_sess); static void port_cleanup(struct vsf_session* p_sess); static void handle_dir_common(struct vsf_session* p_sess, int full_details, int stat_cmd); static void prepend_path_to_filename(struct mystr* p_str); static int get_remote_transfer_fd(struct vsf_session* p_sess, const char* p_status_msg); static void check_abor(struct vsf_session* p_sess); static void handle_sigurg(void* p_private); static void handle_upload_common(struct vsf_session* p_sess, int is_append, int is_unique); static void get_unique_filename(struct mystr* p_outstr, const struct mystr* p_base); static int data_transfer_checks_ok(struct vsf_session* p_sess); static void resolve_tilde(struct mystr* p_str, struct vsf_session* p_sess); void process_post_login(struct vsf_session* p_sess) { str_getcwd(&p_sess->home_str); if (p_sess->is_anonymous) { vsf_sysutil_set_umask(tunable_anon_umask); p_sess->bw_rate_max = tunable_anon_max_rate; } else { vsf_sysutil_set_umask(tunable_local_umask); p_sess->bw_rate_max = tunable_local_max_rate; } if (p_sess->is_http) { handle_http(p_sess); bug("should not be reached"); } /* Don't support async ABOR if we have an SSL channel. The spec says SHOULD * NOT, and I think there are synchronization issues between command and * data reads. */ if (tunable_async_abor_enable && !p_sess->control_use_ssl) { vsf_sysutil_install_sighandler(kVSFSysUtilSigURG, handle_sigurg, p_sess, 0); vsf_sysutil_activate_sigurg(VSFTP_COMMAND_FD); } /* Handle any login message */ vsf_banner_dir_changed(p_sess, FTP_LOGINOK); vsf_cmdio_write(p_sess, FTP_LOGINOK, "Login successful."); while(1) { int cmd_ok = 1; if (tunable_setproctitle_enable) { vsf_sysutil_setproctitle("IDLE"); } /* Blocks */ vsf_cmdio_get_cmd_and_arg(p_sess, &p_sess->ftp_cmd_str, &p_sess->ftp_arg_str, 1); if (tunable_setproctitle_enable) { struct mystr proctitle_str = INIT_MYSTR; str_copy(&proctitle_str, &p_sess->ftp_cmd_str); if (!str_isempty(&p_sess->ftp_arg_str)) { str_append_char(&proctitle_str, ' '); str_append_str(&proctitle_str, &p_sess->ftp_arg_str); } /* Suggestion from Solar */ str_replace_unprintable(&proctitle_str, '?'); vsf_sysutil_setproctitle_str(&proctitle_str); str_free(&proctitle_str); } /* Test command against the allowed lists.. */ if (tunable_cmds_allowed) { static struct mystr s_src_str; static struct mystr s_rhs_str; str_alloc_text(&s_src_str, tunable_cmds_allowed); while (1) { str_split_char(&s_src_str, &s_rhs_str, ','); if (str_isempty(&s_src_str)) { cmd_ok = 0; break; } else if (str_equal(&s_src_str, &p_sess->ftp_cmd_str)) { break; } str_copy(&s_src_str, &s_rhs_str); } } if (tunable_cmds_denied) { static struct mystr s_src_str; static struct mystr s_rhs_str; str_alloc_text(&s_src_str, tunable_cmds_denied); while (1) { str_split_char(&s_src_str, &s_rhs_str, ','); if (str_isempty(&s_src_str)) { break; } else if (str_equal(&s_src_str, &p_sess->ftp_cmd_str)) { cmd_ok = 0; break; } str_copy(&s_src_str, &s_rhs_str); } } if (!cmd_ok) { vsf_cmdio_write(p_sess, FTP_NOPERM, "Permission denied."); } else if (str_equal_text(&p_sess->ftp_cmd_str, "QUIT")) { vsf_cmdio_write_exit(p_sess, FTP_GOODBYE, "Goodbye.", 0); } else if (str_equal_text(&p_sess->ftp_cmd_str, "PWD") || str_equal_text(&p_sess->ftp_cmd_str, "XPWD")) { handle_pwd(p_sess); } else if (str_equal_text(&p_sess->ftp_cmd_str, "CWD") || str_equal_text(&p_sess->ftp_cmd_str, "XCWD")) { handle_cwd(p_sess); } else if (str_equal_text(&p_sess->ftp_cmd_str, "CDUP") || str_equal_text(&p_sess->ftp_cmd_str, "XCUP")) { handle_cdup(p_sess); } else if (tunable_pasv_enable && !p_sess->epsv_all && (str_equal_text(&p_sess->ftp_cmd_str, "PASV") || str_equal_text(&p_sess->ftp_cmd_str, "P@SW"))) { handle_pasv(p_sess, 0); } else if (tunable_pasv_enable && str_equal_text(&p_sess->ftp_cmd_str, "EPSV")) { handle_pasv(p_sess, 1); } else if (tunable_download_enable && str_equal_text(&p_sess->ftp_cmd_str, "RETR")) { handle_retr(p_sess, 0); } else if (str_equal_text(&p_sess->ftp_cmd_str, "NOOP")) { vsf_cmdio_write(p_sess, FTP_NOOPOK, "NOOP ok."); } else if (str_equal_text(&p_sess->ftp_cmd_str, "SYST")) { vsf_cmdio_write(p_sess, FTP_SYSTOK, "UNIX Type: L8"); } else if (str_equal_text(&p_sess->ftp_cmd_str, "HELP")) { handle_help(p_sess); } else if (tunable_dirlist_enable && str_equal_text(&p_sess->ftp_cmd_str, "LIST")) { handle_list(p_sess); } else if (str_equal_text(&p_sess->ftp_cmd_str, "TYPE")) { handle_type(p_sess); } else if (tunable_port_enable && !p_sess->epsv_all && str_equal_text(&p_sess->ftp_cmd_str, "PORT")) { handle_port(p_sess); } else if (tunable_write_enable && (tunable_anon_upload_enable || !p_sess->is_anonymous) && str_equal_text(&p_sess->ftp_cmd_str, "STOR")) { handle_stor(p_sess); } else if (tunable_write_enable && (tunable_anon_mkdir_write_enable || !p_sess->is_anonymous) && (str_equal_text(&p_sess->ftp_cmd_str, "MKD") || str_equal_text(&p_sess->ftp_cmd_str, "XMKD"))) { handle_mkd(p_sess); } else if (tunable_write_enable && (tunable_anon_other_write_enable || !p_sess->is_anonymous) && (str_equal_text(&p_sess->ftp_cmd_str, "RMD") || str_equal_text(&p_sess->ftp_cmd_str, "XRMD"))) { handle_rmd(p_sess); } else if (tunable_write_enable && (tunable_anon_other_write_enable || !p_sess->is_anonymous) && str_equal_text(&p_sess->ftp_cmd_str, "DELE")) { handle_dele(p_sess); } else if (str_equal_text(&p_sess->ftp_cmd_str, "REST")) { handle_rest(p_sess); } else if (tunable_write_enable && (tunable_anon_other_write_enable || !p_sess->is_anonymous) && str_equal_text(&p_sess->ftp_cmd_str, "RNFR")) { handle_rnfr(p_sess); } else if (tunable_write_enable && (tunable_anon_other_write_enable || !p_sess->is_anonymous) && str_equal_text(&p_sess->ftp_cmd_str, "RNTO")) { handle_rnto(p_sess); } else if (tunable_dirlist_enable && str_equal_text(&p_sess->ftp_cmd_str, "NLST")) { handle_nlst(p_sess); } else if (str_equal_text(&p_sess->ftp_cmd_str, "SIZE")) { handle_size(p_sess); } else if (!p_sess->is_anonymous && str_equal_text(&p_sess->ftp_cmd_str, "SITE")) { handle_site(p_sess); } /* Note - the weird ABOR string is checking for an async ABOR arriving * without a SIGURG condition. */ else if (str_equal_text(&p_sess->ftp_cmd_str, "ABOR") || str_equal_text(&p_sess->ftp_cmd_str, "\377\364\377\362ABOR")) { vsf_cmdio_write(p_sess, FTP_ABOR_NOCONN, "No transfer to ABOR."); } else if (tunable_write_enable && (tunable_anon_other_write_enable || !p_sess->is_anonymous) && str_equal_text(&p_sess->ftp_cmd_str, "APPE")) { handle_appe(p_sess); } else if (str_equal_text(&p_sess->ftp_cmd_str, "MDTM")) { handle_mdtm(p_sess); } else if (tunable_port_enable && str_equal_text(&p_sess->ftp_cmd_str, "EPRT")) { handle_eprt(p_sess); } else if (str_equal_text(&p_sess->ftp_cmd_str, "STRU")) { str_upper(&p_sess->ftp_arg_str); if (str_equal_text(&p_sess->ftp_arg_str, "F")) { vsf_cmdio_write(p_sess, FTP_STRUOK, "Structure set to F."); } else { vsf_cmdio_write(p_sess, FTP_BADSTRU, "Bad STRU command."); } } else if (str_equal_text(&p_sess->ftp_cmd_str, "MODE")) { str_upper(&p_sess->ftp_arg_str); if (str_equal_text(&p_sess->ftp_arg_str, "S")) { vsf_cmdio_write(p_sess, FTP_MODEOK, "Mode set to S."); } else { vsf_cmdio_write(p_sess, FTP_BADMODE, "Bad MODE command."); } } else if (tunable_write_enable && (tunable_anon_upload_enable || !p_sess->is_anonymous) && str_equal_text(&p_sess->ftp_cmd_str, "STOU")) { handle_stou(p_sess); } else if (str_equal_text(&p_sess->ftp_cmd_str, "ALLO")) { vsf_cmdio_write(p_sess, FTP_ALLOOK, "ALLO command ignored."); } else if (str_equal_text(&p_sess->ftp_cmd_str, "REIN")) { vsf_cmdio_write(p_sess, FTP_COMMANDNOTIMPL, "REIN not implemented."); } else if (str_equal_text(&p_sess->ftp_cmd_str, "ACCT")) { vsf_cmdio_write(p_sess, FTP_COMMANDNOTIMPL, "ACCT not implemented."); } else if (str_equal_text(&p_sess->ftp_cmd_str, "SMNT")) { vsf_cmdio_write(p_sess, FTP_COMMANDNOTIMPL, "SMNT not implemented."); } else if (str_equal_text(&p_sess->ftp_cmd_str, "FEAT")) { handle_feat(p_sess); } else if (str_equal_text(&p_sess->ftp_cmd_str, "OPTS")) { handle_opts(p_sess); } else if (str_equal_text(&p_sess->ftp_cmd_str, "STAT") && str_isempty(&p_sess->ftp_arg_str)) { handle_stat(p_sess); } else if (tunable_dirlist_enable && str_equal_text(&p_sess->ftp_cmd_str, "STAT")) { handle_stat_file(p_sess); } else if (tunable_ssl_enable && str_equal_text(&p_sess->ftp_cmd_str, "PBSZ")) { handle_pbsz(p_sess); } else if (tunable_ssl_enable && str_equal_text(&p_sess->ftp_cmd_str, "PROT")) { handle_prot(p_sess); } else if (str_equal_text(&p_sess->ftp_cmd_str, "USER")) { handle_logged_in_user(p_sess); } else if (str_equal_text(&p_sess->ftp_cmd_str, "PASS")) { handle_logged_in_pass(p_sess); } else if (str_equal_text(&p_sess->ftp_cmd_str, "PASV") || str_equal_text(&p_sess->ftp_cmd_str, "PORT") || str_equal_text(&p_sess->ftp_cmd_str, "STOR") || str_equal_text(&p_sess->ftp_cmd_str, "MKD") || str_equal_text(&p_sess->ftp_cmd_str, "XMKD") || str_equal_text(&p_sess->ftp_cmd_str, "RMD") || str_equal_text(&p_sess->ftp_cmd_str, "XRMD") || str_equal_text(&p_sess->ftp_cmd_str, "DELE") || str_equal_text(&p_sess->ftp_cmd_str, "RNFR") || str_equal_text(&p_sess->ftp_cmd_str, "RNTO") || str_equal_text(&p_sess->ftp_cmd_str, "SITE") || str_equal_text(&p_sess->ftp_cmd_str, "APPE") || str_equal_text(&p_sess->ftp_cmd_str, "EPSV") || str_equal_text(&p_sess->ftp_cmd_str, "EPRT") || str_equal_text(&p_sess->ftp_cmd_str, "RETR") || str_equal_text(&p_sess->ftp_cmd_str, "LIST") || str_equal_text(&p_sess->ftp_cmd_str, "NLST") || str_equal_text(&p_sess->ftp_cmd_str, "STOU") || str_equal_text(&p_sess->ftp_cmd_str, "ALLO") || str_equal_text(&p_sess->ftp_cmd_str, "REIN") || str_equal_text(&p_sess->ftp_cmd_str, "ACCT") || str_equal_text(&p_sess->ftp_cmd_str, "SMNT") || str_equal_text(&p_sess->ftp_cmd_str, "FEAT") || str_equal_text(&p_sess->ftp_cmd_str, "OPTS") || str_equal_text(&p_sess->ftp_cmd_str, "STAT") || str_equal_text(&p_sess->ftp_cmd_str, "PBSZ") || str_equal_text(&p_sess->ftp_cmd_str, "PROT")) { vsf_cmdio_write(p_sess, FTP_NOPERM, "Permission denied."); } else if (str_isempty(&p_sess->ftp_cmd_str) && str_isempty(&p_sess->ftp_arg_str)) { /* Deliberately ignore to avoid NAT device bugs. ProFTPd does the same. */ } else if (str_equal_text(&p_sess->ftp_cmd_str, "GET") || str_equal_text(&p_sess->ftp_cmd_str, "POST") || str_equal_text(&p_sess->ftp_cmd_str, "HEAD") || str_equal_text(&p_sess->ftp_cmd_str, "OPTIONS") || str_equal_text(&p_sess->ftp_cmd_str, "CONNECT")) { vsf_cmdio_write_exit(p_sess, FTP_BADCMD, "HTTP protocol commands not allowed.", 1); } else { vsf_cmdio_write(p_sess, FTP_BADCMD, "Unknown command."); } if (vsf_log_entry_pending(p_sess)) { vsf_log_do_log(p_sess, 0); } if (p_sess->data_timeout) { vsf_cmdio_write_exit(p_sess, FTP_DATA_TIMEOUT, "Data timeout. Reconnect. Sorry.", 1); } } } static void handle_pwd(struct vsf_session* p_sess) { static struct mystr s_cwd_buf_mangle_str; static struct mystr s_pwd_res_str; str_getcwd(&s_cwd_buf_mangle_str); /* Double up any double-quotes in the pathname! */ str_replace_text(&s_cwd_buf_mangle_str, "\"", "\"\""); /* Enclose pathname in quotes */ str_alloc_text(&s_pwd_res_str, "\""); str_append_str(&s_pwd_res_str, &s_cwd_buf_mangle_str); str_append_text(&s_pwd_res_str, "\" is the current directory"); vsf_cmdio_write_str(p_sess, FTP_PWDOK, &s_pwd_res_str); } static void handle_cwd(struct vsf_session* p_sess) { int retval; resolve_tilde(&p_sess->ftp_arg_str, p_sess); if (!vsf_access_check_file(&p_sess->ftp_arg_str)) { vsf_cmdio_write(p_sess, FTP_NOPERM, "Permission denied."); return; } retval = str_chdir(&p_sess->ftp_arg_str); if (retval == 0) { /* Handle any messages */ vsf_banner_dir_changed(p_sess, FTP_CWDOK); vsf_cmdio_write(p_sess, FTP_CWDOK, "Directory successfully changed."); } else { vsf_cmdio_write(p_sess, FTP_FILEFAIL, "Failed to change directory."); } } static void handle_cdup(struct vsf_session* p_sess) { str_alloc_text(&p_sess->ftp_arg_str, ".."); handle_cwd(p_sess); } static int port_active(struct vsf_session* p_sess) { int ret = 0; if (p_sess->p_port_sockaddr != 0) { ret = 1; if (pasv_active(p_sess)) { bug("port and pasv both active"); } } return ret; } static int pasv_active(struct vsf_session* p_sess) { int ret = 0; if (tunable_one_process_model) { ret = vsf_one_process_pasv_active(p_sess); } else { ret = vsf_two_process_pasv_active(p_sess); } if (ret) { if (port_active(p_sess)) { bug("pasv and port both active"); } } return ret; } static void port_cleanup(struct vsf_session* p_sess) { vsf_sysutil_sockaddr_clear(&p_sess->p_port_sockaddr); } static void pasv_cleanup(struct vsf_session* p_sess) { if (tunable_one_process_model) { vsf_one_process_pasv_cleanup(p_sess); } else { vsf_two_process_pasv_cleanup(p_sess); } } static void handle_pasv(struct vsf_session* p_sess, int is_epsv) { unsigned short the_port; static struct mystr s_pasv_res_str; static struct vsf_sysutil_sockaddr* s_p_sockaddr; int is_ipv6 = vsf_sysutil_sockaddr_is_ipv6(p_sess->p_local_addr); if (is_epsv && !str_isempty(&p_sess->ftp_arg_str)) { int argval; str_upper(&p_sess->ftp_arg_str); if (str_equal_text(&p_sess->ftp_arg_str, "ALL")) { p_sess->epsv_all = 1; vsf_cmdio_write(p_sess, FTP_EPSVALLOK, "EPSV ALL ok."); return; } argval = vsf_sysutil_atoi(str_getbuf(&p_sess->ftp_arg_str)); if (argval < 1 || argval > 2 || (!is_ipv6 && argval == 2)) { vsf_cmdio_write(p_sess, FTP_EPSVBAD, "Bad network protocol."); return; } } pasv_cleanup(p_sess); port_cleanup(p_sess); if (tunable_one_process_model) { the_port = vsf_one_process_listen(p_sess); } else { the_port = vsf_two_process_listen(p_sess); } if (is_epsv) { str_alloc_text(&s_pasv_res_str, "Entering Extended Passive Mode (|||"); str_append_ulong(&s_pasv_res_str, (unsigned long) the_port); str_append_text(&s_pasv_res_str, "|)"); vsf_cmdio_write_str(p_sess, FTP_EPSVOK, &s_pasv_res_str); return; } if (tunable_pasv_address != 0) { vsf_sysutil_sockaddr_alloc_ipv4(&s_p_sockaddr); /* Report passive address as specified in configuration */ if (vsf_sysutil_inet_aton(tunable_pasv_address, s_p_sockaddr) == 0) { die("invalid pasv_address"); } } else { vsf_sysutil_sockaddr_clone(&s_p_sockaddr, p_sess->p_local_addr); } str_alloc_text(&s_pasv_res_str, "Entering Passive Mode ("); if (!is_ipv6) { str_append_text(&s_pasv_res_str, vsf_sysutil_inet_ntop(s_p_sockaddr)); } else { const void* p_v4addr = vsf_sysutil_sockaddr_ipv6_v4(s_p_sockaddr); if (p_v4addr) { str_append_text(&s_pasv_res_str, vsf_sysutil_inet_ntoa(p_v4addr)); } else { str_append_text(&s_pasv_res_str, "0,0,0,0"); } } str_replace_char(&s_pasv_res_str, '.', ','); str_append_text(&s_pasv_res_str, ","); str_append_ulong(&s_pasv_res_str, the_port >> 8); str_append_text(&s_pasv_res_str, ","); str_append_ulong(&s_pasv_res_str, the_port & 255); str_append_text(&s_pasv_res_str, ")."); vsf_cmdio_write_str(p_sess, FTP_PASVOK, &s_pasv_res_str); } static void handle_retr(struct vsf_session* p_sess, int is_http) { static struct mystr s_mark_str; static struct vsf_sysutil_statbuf* s_p_statbuf; struct vsf_transfer_ret trans_ret; int remote_fd; int opened_file; int is_ascii = 0; filesize_t offset = p_sess->restart_pos; p_sess->restart_pos = 0; if (!is_http && !data_transfer_checks_ok(p_sess)) { return; } if (p_sess->is_ascii && offset != 0) { vsf_cmdio_write(p_sess, FTP_FILEFAIL, "No support for resume of ASCII transfer."); return; } resolve_tilde(&p_sess->ftp_arg_str, p_sess); vsf_log_start_entry(p_sess, kVSFLogEntryDownload); str_copy(&p_sess->log_str, &p_sess->ftp_arg_str); prepend_path_to_filename(&p_sess->log_str); if (!vsf_access_check_file(&p_sess->ftp_arg_str)) { vsf_cmdio_write(p_sess, FTP_NOPERM, "Permission denied."); return; } opened_file = str_open(&p_sess->ftp_arg_str, kVSFSysStrOpenReadOnly); if (vsf_sysutil_retval_is_error(opened_file)) { vsf_cmdio_write(p_sess, FTP_FILEFAIL, "Failed to open file."); return; } /* Lock file if required */ if (tunable_lock_upload_files) { vsf_sysutil_lock_file_read(opened_file); } vsf_sysutil_fstat(opened_file, &s_p_statbuf); /* No games please */ if (!vsf_sysutil_statbuf_is_regfile(s_p_statbuf)) { /* Note - pretend open failed */ vsf_cmdio_write(p_sess, FTP_FILEFAIL, "Failed to open file."); /* Irritating FireFox does RETR on directories, so avoid logging this * very common and noisy case. */ if (vsf_sysutil_statbuf_is_dir(s_p_statbuf)) { vsf_log_clear_entry(p_sess); } goto file_close_out; } /* Now deactive O_NONBLOCK, otherwise we have a problem on DMAPI filesystems * such as XFS DMAPI. */ vsf_sysutil_deactivate_noblock(opened_file); /* Optionally, we'll be paranoid and only serve publicly readable stuff */ if (p_sess->is_anonymous && tunable_anon_world_readable_only && !vsf_sysutil_statbuf_is_readable_other(s_p_statbuf)) { vsf_cmdio_write(p_sess, FTP_FILEFAIL, "Failed to open file."); goto file_close_out; } /* Set the download offset (from REST) if any */ if (offset != 0) { vsf_sysutil_lseek_to(opened_file, offset); } str_alloc_text(&s_mark_str, "Opening "); if (tunable_ascii_download_enable && p_sess->is_ascii) { str_append_text(&s_mark_str, "ASCII"); is_ascii = 1; } else { str_append_text(&s_mark_str, "BINARY"); } str_append_text(&s_mark_str, " mode data connection for "); str_append_str(&s_mark_str, &p_sess->ftp_arg_str); str_append_text(&s_mark_str, " ("); str_append_filesize_t(&s_mark_str, vsf_sysutil_statbuf_get_size(s_p_statbuf)); str_append_text(&s_mark_str, " bytes)."); if (is_http) { remote_fd = VSFTP_COMMAND_FD; } else { remote_fd = get_remote_transfer_fd(p_sess, str_getbuf(&s_mark_str)); if (vsf_sysutil_retval_is_error(remote_fd)) { goto port_pasv_cleanup_out; } } trans_ret = vsf_ftpdataio_transfer_file(p_sess, remote_fd, opened_file, 0, is_ascii); if (!is_http && vsf_ftpdataio_dispose_transfer_fd(p_sess) != 1 && trans_ret.retval == 0) { trans_ret.retval = -2; } p_sess->transfer_size = trans_ret.transferred; /* Log _after_ the blocking dispose call, so we get transfer times right */ if (trans_ret.retval == 0) { vsf_log_do_log(p_sess, 1); } if (is_http) { goto file_close_out; } /* Emit status message _after_ blocking dispose call to avoid buggy FTP * clients truncating the transfer. */ if (trans_ret.retval == -1) { vsf_cmdio_write(p_sess, FTP_BADSENDFILE, "Failure reading local file."); } else if (trans_ret.retval == -2) { if (!p_sess->data_timeout) { vsf_cmdio_write(p_sess, FTP_BADSENDNET, "Failure writing network stream."); } } else { vsf_cmdio_write(p_sess, FTP_TRANSFEROK, "Transfer complete."); } check_abor(p_sess); port_pasv_cleanup_out: port_cleanup(p_sess); pasv_cleanup(p_sess); file_close_out: vsf_sysutil_close(opened_file); } static void handle_list(struct vsf_session* p_sess) { handle_dir_common(p_sess, 1, 0); } static void handle_dir_common(struct vsf_session* p_sess, int full_details, int stat_cmd) { static struct mystr s_option_str; static struct mystr s_filter_str; static struct mystr s_dir_name_str; static struct vsf_sysutil_statbuf* s_p_dirstat; int dir_allow_read = 1; struct vsf_sysutil_dir* p_dir = 0; int retval = 0; int use_control = 0; str_empty(&s_option_str); str_empty(&s_filter_str); /* By default open the current directory */ str_alloc_text(&s_dir_name_str, "."); if (!stat_cmd && !data_transfer_checks_ok(p_sess)) { return; } /* Do we have an option? Going to be strict here - the option must come * first. e.g. "ls -a .." fine, "ls .. -a" not fine */ if (!str_isempty(&p_sess->ftp_arg_str) && str_get_char_at(&p_sess->ftp_arg_str, 0) == '-') { /* Chop off the '-' */ str_mid_to_end(&p_sess->ftp_arg_str, &s_option_str, 1); /* A space will separate options from filter (if any) */ str_split_char(&s_option_str, &s_filter_str, ' '); } else { /* The argument, if any, is just a filter */ str_copy(&s_filter_str, &p_sess->ftp_arg_str); } if (!str_isempty(&s_filter_str)) { resolve_tilde(&s_filter_str, p_sess); if (!vsf_access_check_file(&s_filter_str)) { vsf_cmdio_write(p_sess, FTP_NOPERM, "Permission denied."); return; } /* First check - is it an outright directory, as in "ls /pub" */ p_dir = str_opendir(&s_filter_str); if (p_dir != 0) { /* Listing a directory! */ str_copy(&s_dir_name_str, &s_filter_str); str_free(&s_filter_str); } else { struct str_locate_result locate_result = str_locate_char(&s_filter_str, '/'); if (locate_result.found) { /* Includes a path! Reverse scan for / in the arg, to get the * base directory and filter (if any) */ str_copy(&s_dir_name_str, &s_filter_str); str_split_char_reverse(&s_dir_name_str, &s_filter_str, '/'); /* If we have e.g. "ls /.message", we just ripped off the leading * slash because it is the only one! */ if (str_isempty(&s_dir_name_str)) { str_alloc_text(&s_dir_name_str, "/"); } } } } if (p_dir == 0) { /* NOTE - failure check done below, it's not forgotten */ p_dir = str_opendir(&s_dir_name_str); } /* Fine, do it */ if (stat_cmd) { use_control = 1; str_append_char(&s_option_str, 'a'); vsf_cmdio_write_hyphen(p_sess, FTP_STATFILE_OK, "Status follows:"); } else { int remote_fd = get_remote_transfer_fd( p_sess, "Here comes the directory listing."); if (vsf_sysutil_retval_is_error(remote_fd)) { goto dir_close_out; } } if (p_sess->is_anonymous && p_dir && tunable_anon_world_readable_only) { vsf_sysutil_dir_stat(p_dir, &s_p_dirstat); if (!vsf_sysutil_statbuf_is_readable_other(s_p_dirstat)) { dir_allow_read = 0; } } if (p_dir != 0 && dir_allow_read) { retval = vsf_ftpdataio_transfer_dir(p_sess, use_control, p_dir, &s_dir_name_str, &s_option_str, &s_filter_str, full_details); } if (!stat_cmd) { if (vsf_ftpdataio_dispose_transfer_fd(p_sess) != 1 && retval == 0) { retval = -1; } } if (stat_cmd) { vsf_cmdio_write(p_sess, FTP_STATFILE_OK, "End of status"); } else if (retval != 0) { if (!p_sess->data_timeout) { vsf_cmdio_write(p_sess, FTP_BADSENDNET, "Failure writing network stream."); } } else if (p_dir == 0 || !dir_allow_read) { vsf_cmdio_write(p_sess, FTP_TRANSFEROK, "Transfer done (but failed to open directory)."); } else { vsf_cmdio_write(p_sess, FTP_TRANSFEROK, "Directory send OK."); } check_abor(p_sess); dir_close_out: if (p_dir) { vsf_sysutil_closedir(p_dir); } if (!stat_cmd) { port_cleanup(p_sess); pasv_cleanup(p_sess); } } static void handle_type(struct vsf_session* p_sess) { str_upper(&p_sess->ftp_arg_str); if (str_equal_text(&p_sess->ftp_arg_str, "I") || str_equal_text(&p_sess->ftp_arg_str, "L8") || str_equal_text(&p_sess->ftp_arg_str, "L 8")) { p_sess->is_ascii = 0; vsf_cmdio_write(p_sess, FTP_TYPEOK, "Switching to Binary mode."); } else if (str_equal_text(&p_sess->ftp_arg_str, "A") || str_equal_text(&p_sess->ftp_arg_str, "A N")) { p_sess->is_ascii = 1; vsf_cmdio_write(p_sess, FTP_TYPEOK, "Switching to ASCII mode."); } else { vsf_cmdio_write(p_sess, FTP_BADCMD, "Unrecognised TYPE command."); } } static void handle_port(struct vsf_session* p_sess) { unsigned short the_port; unsigned char vals[6]; const unsigned char* p_raw; pasv_cleanup(p_sess); port_cleanup(p_sess); p_raw = vsf_sysutil_parse_uchar_string_sep(&p_sess->ftp_arg_str, ',', vals, sizeof(vals)); if (p_raw == 0) { vsf_cmdio_write(p_sess, FTP_BADCMD, "Illegal PORT command."); return; } the_port = (unsigned short) ((vals[4] << 8) | vals[5]); vsf_sysutil_sockaddr_clone(&p_sess->p_port_sockaddr, p_sess->p_local_addr); vsf_sysutil_sockaddr_set_ipv4addr(p_sess->p_port_sockaddr, vals); vsf_sysutil_sockaddr_set_port(p_sess->p_port_sockaddr, the_port); /* SECURITY: * 1) Reject requests not connecting to the control socket IP * 2) Reject connects to privileged ports */ if (!tunable_port_promiscuous) { if (!vsf_sysutil_sockaddr_addr_equal(p_sess->p_remote_addr, p_sess->p_port_sockaddr) || vsf_sysutil_is_port_reserved(the_port)) { vsf_cmdio_write(p_sess, FTP_BADCMD, "Illegal PORT command."); port_cleanup(p_sess); return; } } vsf_cmdio_write(p_sess, FTP_PORTOK, "PORT command successful. Consider using PASV."); } static void handle_stor(struct vsf_session* p_sess) { handle_upload_common(p_sess, 0, 0); } static void handle_upload_common(struct vsf_session* p_sess, int is_append, int is_unique) { static struct vsf_sysutil_statbuf* s_p_statbuf; static struct mystr s_filename; struct mystr* p_filename; struct vsf_transfer_ret trans_ret; int new_file_fd; int remote_fd; int success = 0; int created = 0; int do_truncate = 0; filesize_t offset = p_sess->restart_pos; p_sess->restart_pos = 0; if (!data_transfer_checks_ok(p_sess)) { return; } resolve_tilde(&p_sess->ftp_arg_str, p_sess); p_filename = &p_sess->ftp_arg_str; if (is_unique) { get_unique_filename(&s_filename, p_filename); p_filename = &s_filename; } vsf_log_start_entry(p_sess, kVSFLogEntryUpload); str_copy(&p_sess->log_str, &p_sess->ftp_arg_str); prepend_path_to_filename(&p_sess->log_str); if (!vsf_access_check_file(p_filename)) { vsf_cmdio_write(p_sess, FTP_NOPERM, "Permission denied."); return; } /* NOTE - actual file permissions will be governed by the tunable umask */ /* XXX - do we care about race between create and chown() of anonymous * upload? */ if (is_unique || (p_sess->is_anonymous && !tunable_anon_other_write_enable)) { new_file_fd = str_create_exclusive(p_filename); } else { /* For non-anonymous, allow open() to overwrite or append existing files */ new_file_fd = str_create(p_filename); if (!is_append && offset == 0) { do_truncate = 1; } } if (vsf_sysutil_retval_is_error(new_file_fd)) { vsf_cmdio_write(p_sess, FTP_UPLOADFAIL, "Could not create file."); return; } created = 1; vsf_sysutil_fstat(new_file_fd, &s_p_statbuf); if (vsf_sysutil_statbuf_is_regfile(s_p_statbuf)) { /* Now deactive O_NONBLOCK, otherwise we have a problem on DMAPI filesystems * such as XFS DMAPI. */ vsf_sysutil_deactivate_noblock(new_file_fd); } /* Are we required to chown() this file for security? */ if (p_sess->is_anonymous && tunable_chown_uploads) { vsf_sysutil_fchmod(new_file_fd, tunable_chown_upload_mode); if (tunable_one_process_model) { vsf_one_process_chown_upload(p_sess, new_file_fd); } else { vsf_two_process_chown_upload(p_sess, new_file_fd); } } /* Are we required to lock this file? */ if (tunable_lock_upload_files) { vsf_sysutil_lock_file_write(new_file_fd); } /* Must truncate the file AFTER locking it! */ if (do_truncate) { vsf_sysutil_ftruncate(new_file_fd); vsf_sysutil_lseek_to(new_file_fd, 0); } if (!is_append && offset != 0) { /* XXX - warning, allows seek past end of file! Check for seek > size? */ vsf_sysutil_lseek_to(new_file_fd, offset); } else if (is_append) { vsf_sysutil_lseek_end(new_file_fd); } if (is_unique) { struct mystr resp_str = INIT_MYSTR; str_alloc_text(&resp_str, "FILE: "); str_append_str(&resp_str, p_filename); remote_fd = get_remote_transfer_fd(p_sess, str_getbuf(&resp_str)); str_free(&resp_str); } else { remote_fd = get_remote_transfer_fd(p_sess, "Ok to send data."); } if (vsf_sysutil_retval_is_error(remote_fd)) { goto port_pasv_cleanup_out; } if (tunable_ascii_upload_enable && p_sess->is_ascii) { trans_ret = vsf_ftpdataio_transfer_file(p_sess, remote_fd, new_file_fd, 1, 1); } else { trans_ret = vsf_ftpdataio_transfer_file(p_sess, remote_fd, new_file_fd, 1, 0); } if (vsf_ftpdataio_dispose_transfer_fd(p_sess) != 1 && trans_ret.retval == 0) { trans_ret.retval = -2; } p_sess->transfer_size = trans_ret.transferred; if (trans_ret.retval == 0) { success = 1; vsf_log_do_log(p_sess, 1); } if (trans_ret.retval == -1) { vsf_cmdio_write(p_sess, FTP_BADSENDFILE, "Failure writing to local file."); } else if (trans_ret.retval == -2) { if (!p_sess->data_timeout) { vsf_cmdio_write(p_sess, FTP_BADSENDNET, "Failure reading network stream."); } } else { vsf_cmdio_write(p_sess, FTP_TRANSFEROK, "Transfer complete."); } check_abor(p_sess); port_pasv_cleanup_out: port_cleanup(p_sess); pasv_cleanup(p_sess); if (tunable_delete_failed_uploads && created && !success) { str_unlink(p_filename); } vsf_sysutil_close(new_file_fd); } static void handle_mkd(struct vsf_session* p_sess) { int retval; resolve_tilde(&p_sess->ftp_arg_str, p_sess); vsf_log_start_entry(p_sess, kVSFLogEntryMkdir); str_copy(&p_sess->log_str, &p_sess->ftp_arg_str); prepend_path_to_filename(&p_sess->log_str); if (!vsf_access_check_file(&p_sess->ftp_arg_str)) { vsf_cmdio_write(p_sess, FTP_NOPERM, "Permission denied."); return; } /* NOTE! Actual permissions will be governed by the tunable umask */ retval = str_mkdir(&p_sess->ftp_arg_str, 0777); if (retval != 0) { vsf_cmdio_write(p_sess, FTP_FILEFAIL, "Create directory operation failed."); return; } vsf_log_do_log(p_sess, 1); { static struct mystr s_mkd_res; static struct mystr s_tmp_str; str_copy(&s_tmp_str, &p_sess->ftp_arg_str); prepend_path_to_filename(&s_tmp_str); /* Double up double quotes */ str_replace_text(&s_tmp_str, "\"", "\"\""); /* Build result string */ str_alloc_text(&s_mkd_res, "\""); str_append_str(&s_mkd_res, &s_tmp_str); str_append_text(&s_mkd_res, "\" created"); vsf_cmdio_write_str(p_sess, FTP_MKDIROK, &s_mkd_res); } } static void handle_rmd(struct vsf_session* p_sess) { int retval; resolve_tilde(&p_sess->ftp_arg_str, p_sess); vsf_log_start_entry(p_sess, kVSFLogEntryRmdir); str_copy(&p_sess->log_str, &p_sess->ftp_arg_str); prepend_path_to_filename(&p_sess->log_str); if (!vsf_access_check_file(&p_sess->ftp_arg_str)) { vsf_cmdio_write(p_sess, FTP_NOPERM, "Permission denied."); return; } retval = str_rmdir(&p_sess->ftp_arg_str); if (retval != 0) { vsf_cmdio_write(p_sess, FTP_FILEFAIL, "Remove directory operation failed."); } else { vsf_log_do_log(p_sess, 1); vsf_cmdio_write(p_sess, FTP_RMDIROK, "Remove directory operation successful."); } } static void handle_dele(struct vsf_session* p_sess) { int retval; resolve_tilde(&p_sess->ftp_arg_str, p_sess); vsf_log_start_entry(p_sess, kVSFLogEntryDelete); str_copy(&p_sess->log_str, &p_sess->ftp_arg_str); prepend_path_to_filename(&p_sess->log_str); if (!vsf_access_check_file(&p_sess->ftp_arg_str)) { vsf_cmdio_write(p_sess, FTP_NOPERM, "Permission denied."); return; } retval = str_unlink(&p_sess->ftp_arg_str); if (retval != 0) { vsf_cmdio_write(p_sess, FTP_FILEFAIL, "Delete operation failed."); } else { vsf_log_do_log(p_sess, 1); vsf_cmdio_write(p_sess, FTP_DELEOK, "Delete operation successful."); } } static void handle_rest(struct vsf_session* p_sess) { static struct mystr s_rest_str; filesize_t val = str_a_to_filesize_t(&p_sess->ftp_arg_str); if (val < 0) { val = 0; } p_sess->restart_pos = val; str_alloc_text(&s_rest_str, "Restart position accepted ("); str_append_filesize_t(&s_rest_str, val); str_append_text(&s_rest_str, ")."); vsf_cmdio_write_str(p_sess, FTP_RESTOK, &s_rest_str); } static void handle_rnfr(struct vsf_session* p_sess) { static struct vsf_sysutil_statbuf* p_statbuf; int retval; /* Clear old value */ str_free(&p_sess->rnfr_filename_str); resolve_tilde(&p_sess->ftp_arg_str, p_sess); if (!vsf_access_check_file(&p_sess->ftp_arg_str)) { vsf_log_start_entry(p_sess, kVSFLogEntryRename); str_copy(&p_sess->log_str, &p_sess->ftp_arg_str); prepend_path_to_filename(&p_sess->log_str); vsf_cmdio_write(p_sess, FTP_NOPERM, "Permission denied."); return; } /* Does it exist? */ retval = str_stat(&p_sess->ftp_arg_str, &p_statbuf); if (retval == 0) { /* Yes */ str_copy(&p_sess->rnfr_filename_str, &p_sess->ftp_arg_str); vsf_cmdio_write(p_sess, FTP_RNFROK, "Ready for RNTO."); } else { vsf_log_start_entry(p_sess, kVSFLogEntryRename); str_copy(&p_sess->log_str, &p_sess->ftp_arg_str); prepend_path_to_filename(&p_sess->log_str); vsf_cmdio_write(p_sess, FTP_FILEFAIL, "RNFR command failed."); } } static void handle_rnto(struct vsf_session* p_sess) { static struct mystr s_tmp_str; int retval; /* If we didn't get a RNFR, throw a wobbly */ if (str_isempty(&p_sess->rnfr_filename_str)) { vsf_cmdio_write(p_sess, FTP_NEEDRNFR, "RNFR required first."); return; } resolve_tilde(&p_sess->ftp_arg_str, p_sess); vsf_log_start_entry(p_sess, kVSFLogEntryRename); str_copy(&p_sess->log_str, &p_sess->rnfr_filename_str); prepend_path_to_filename(&p_sess->log_str); str_append_char(&p_sess->log_str, ' '); str_copy(&s_tmp_str, &p_sess->ftp_arg_str); prepend_path_to_filename(&s_tmp_str); str_append_str(&p_sess->log_str, &s_tmp_str); if (!vsf_access_check_file(&p_sess->ftp_arg_str)) { vsf_cmdio_write(p_sess, FTP_NOPERM, "Permission denied."); return; } /* NOTE - might overwrite destination file. Not a concern because the same * could be accomplished with DELE. */ retval = str_rename(&p_sess->rnfr_filename_str, &p_sess->ftp_arg_str); /* Clear the RNFR filename; start the two stage process again! */ str_free(&p_sess->rnfr_filename_str); if (retval == 0) { vsf_log_do_log(p_sess, 1); vsf_cmdio_write(p_sess, FTP_RENAMEOK, "Rename successful."); } else { vsf_cmdio_write(p_sess, FTP_FILEFAIL, "Rename failed."); } } static void handle_nlst(struct vsf_session* p_sess) { handle_dir_common(p_sess, 0, 0); } static void prepend_path_to_filename(struct mystr* p_str) { static struct mystr s_tmp_str; /* Only prepend current working directory if the incoming filename is * relative */ str_empty(&s_tmp_str); if (str_isempty(p_str) || str_get_char_at(p_str, 0) != '/') { str_getcwd(&s_tmp_str); /* Careful to not emit // if we are in directory / (common with chroot) */ if (str_isempty(&s_tmp_str) || str_get_char_at(&s_tmp_str, str_getlen(&s_tmp_str) - 1) != '/') { str_append_char(&s_tmp_str, '/'); } } str_append_str(&s_tmp_str, p_str); str_copy(p_str, &s_tmp_str); } static void handle_sigurg(void* p_private) { struct mystr async_cmd_str = INIT_MYSTR; struct mystr async_arg_str = INIT_MYSTR; struct mystr real_cmd_str = INIT_MYSTR; unsigned int len; struct vsf_session* p_sess = (struct vsf_session*) p_private; /* Did stupid client sent something OOB without a data connection? */ if (p_sess->data_fd == -1) { return; } /* Get the async command - blocks (use data timeout alarm) */ vsf_cmdio_get_cmd_and_arg(p_sess, &async_cmd_str, &async_arg_str, 0); /* Chop off first four characters; they are telnet characters. The client * should have sent the first two normally and the second two as urgent * data. */ len = str_getlen(&async_cmd_str); if (len >= 4) { str_right(&async_cmd_str, &real_cmd_str, len - 4); } if (str_equal_text(&real_cmd_str, "ABOR")) { p_sess->abor_received = 1; /* This is failok because of a small race condition; the SIGURG might * be raised after the data socket is closed, but before data_fd is * set to -1. */ vsf_sysutil_shutdown_failok(p_sess->data_fd); } else { /* Sorry! */ vsf_cmdio_write(p_sess, FTP_BADCMD, "Unknown command."); } str_free(&async_cmd_str); str_free(&async_arg_str); str_free(&real_cmd_str); } static int get_remote_transfer_fd(struct vsf_session* p_sess, const char* p_status_msg) { int remote_fd; if (!pasv_active(p_sess) && !port_active(p_sess)) { bug("neither PORT nor PASV active in get_remote_transfer_fd"); } p_sess->abor_received = 0; if (pasv_active(p_sess)) { remote_fd = vsf_ftpdataio_get_pasv_fd(p_sess); } else { remote_fd = vsf_ftpdataio_get_port_fd(p_sess); } if (vsf_sysutil_retval_is_error(remote_fd)) { return remote_fd; } vsf_cmdio_write(p_sess, FTP_DATACONN, p_status_msg); if (vsf_ftpdataio_post_mark_connect(p_sess) != 1) { vsf_ftpdataio_dispose_transfer_fd(p_sess); return -1; } return remote_fd; } static void check_abor(struct vsf_session* p_sess) { /* If the client sent ABOR, respond to it here */ if (p_sess->abor_received) { p_sess->abor_received = 0; vsf_cmdio_write(p_sess, FTP_ABOROK, "ABOR successful."); } } static void handle_size(struct vsf_session* p_sess) { /* Note - in ASCII mode, are supposed to return the size after taking into * account ASCII linefeed conversions. At least this is what wu-ftpd does in * version 2.6.1. Proftpd-1.2.0pre fails to do this. * I will not do it because it is a potential I/O DoS. */ static struct vsf_sysutil_statbuf* s_p_statbuf; int retval; resolve_tilde(&p_sess->ftp_arg_str, p_sess); if (!vsf_access_check_file(&p_sess->ftp_arg_str)) { vsf_cmdio_write(p_sess, FTP_NOPERM, "Permission denied."); return; } retval = str_stat(&p_sess->ftp_arg_str, &s_p_statbuf); if (retval != 0 || !vsf_sysutil_statbuf_is_regfile(s_p_statbuf)) { vsf_cmdio_write(p_sess, FTP_FILEFAIL, "Could not get file size."); } else { static struct mystr s_size_res_str; str_alloc_filesize_t(&s_size_res_str, vsf_sysutil_statbuf_get_size(s_p_statbuf)); vsf_cmdio_write_str(p_sess, FTP_SIZEOK, &s_size_res_str); } } static void handle_site(struct vsf_session* p_sess) { static struct mystr s_site_args_str; /* What SITE sub-command is it? */ str_split_char(&p_sess->ftp_arg_str, &s_site_args_str, ' '); str_upper(&p_sess->ftp_arg_str); if (tunable_write_enable && tunable_chmod_enable && str_equal_text(&p_sess->ftp_arg_str, "CHMOD")) { handle_site_chmod(p_sess, &s_site_args_str); } else if (str_equal_text(&p_sess->ftp_arg_str, "UMASK")) { handle_site_umask(p_sess, &s_site_args_str); } else if (str_equal_text(&p_sess->ftp_arg_str, "HELP")) { if (tunable_write_enable && tunable_chmod_enable) { vsf_cmdio_write(p_sess, FTP_SITEHELP, "CHMOD UMASK HELP"); } else { vsf_cmdio_write(p_sess, FTP_SITEHELP, "UMASK HELP"); } } else { vsf_cmdio_write(p_sess, FTP_BADCMD, "Unknown SITE command."); } } static void handle_site_chmod(struct vsf_session* p_sess, struct mystr* p_arg_str) { static struct mystr s_chmod_file_str; unsigned int perms; int retval; if (str_isempty(p_arg_str)) { vsf_cmdio_write(p_sess, FTP_BADCMD, "SITE CHMOD needs 2 arguments."); return; } str_split_char(p_arg_str, &s_chmod_file_str, ' '); if (str_isempty(&s_chmod_file_str)) { vsf_cmdio_write(p_sess, FTP_BADCMD, "SITE CHMOD needs 2 arguments."); return; } resolve_tilde(&s_chmod_file_str, p_sess); vsf_log_start_entry(p_sess, kVSFLogEntryChmod); str_copy(&p_sess->log_str, &s_chmod_file_str); prepend_path_to_filename(&p_sess->log_str); str_append_char(&p_sess->log_str, ' '); str_append_str(&p_sess->log_str, p_arg_str); if (!vsf_access_check_file(&s_chmod_file_str)) { vsf_cmdio_write(p_sess, FTP_NOPERM, "Permission denied."); return; } /* Don't worry - our chmod() implementation only allows 0 - 0777 */ perms = str_octal_to_uint(p_arg_str); retval = str_chmod(&s_chmod_file_str, perms); if (vsf_sysutil_retval_is_error(retval)) { vsf_cmdio_write(p_sess, FTP_FILEFAIL, "SITE CHMOD command failed."); } else { vsf_log_do_log(p_sess, 1); vsf_cmdio_write(p_sess, FTP_CHMODOK, "SITE CHMOD command ok."); } } static void handle_site_umask(struct vsf_session* p_sess, struct mystr* p_arg_str) { static struct mystr s_umask_resp_str; if (str_isempty(p_arg_str)) { /* Empty arg => report current umask */ str_alloc_text(&s_umask_resp_str, "Your current UMASK is "); str_append_text(&s_umask_resp_str, vsf_sysutil_uint_to_octal(vsf_sysutil_get_umask())); } else { /* Set current umask */ unsigned int new_umask = str_octal_to_uint(p_arg_str); vsf_sysutil_set_umask(new_umask); str_alloc_text(&s_umask_resp_str, "UMASK set to "); str_append_text(&s_umask_resp_str, vsf_sysutil_uint_to_octal(vsf_sysutil_get_umask())); } vsf_cmdio_write_str(p_sess, FTP_UMASKOK, &s_umask_resp_str); } static void handle_appe(struct vsf_session* p_sess) { handle_upload_common(p_sess, 1, 0); } static void handle_mdtm(struct vsf_session* p_sess) { static struct mystr s_filename_str; static struct vsf_sysutil_statbuf* s_p_statbuf; int do_write = 0; long modtime = 0; struct str_locate_result loc = str_locate_char(&p_sess->ftp_arg_str, ' '); int retval = str_stat(&p_sess->ftp_arg_str, &s_p_statbuf); if (tunable_mdtm_write && retval != 0 && loc.found && vsf_sysutil_isdigit(str_get_char_at(&p_sess->ftp_arg_str, 0))) { if (loc.index == 8 || loc.index == 14 || (loc.index > 15 && str_get_char_at(&p_sess->ftp_arg_str, 14) == '.')) { do_write = 1; } } if (do_write != 0) { str_split_char(&p_sess->ftp_arg_str, &s_filename_str, ' '); modtime = vsf_sysutil_parse_time(str_getbuf(&p_sess->ftp_arg_str)); str_copy(&p_sess->ftp_arg_str, &s_filename_str); } resolve_tilde(&p_sess->ftp_arg_str, p_sess); if (!vsf_access_check_file(&p_sess->ftp_arg_str)) { vsf_cmdio_write(p_sess, FTP_NOPERM, "Permission denied."); return; } if (do_write && tunable_write_enable && (tunable_anon_other_write_enable || !p_sess->is_anonymous)) { retval = str_stat(&p_sess->ftp_arg_str, &s_p_statbuf); if (retval != 0 || !vsf_sysutil_statbuf_is_regfile(s_p_statbuf)) { vsf_cmdio_write(p_sess, FTP_FILEFAIL, "Could not set file modification time."); } else { retval = vsf_sysutil_setmodtime( str_getbuf(&p_sess->ftp_arg_str), modtime, tunable_use_localtime); if (retval != 0) { vsf_cmdio_write(p_sess, FTP_FILEFAIL, "Could not set file modification time."); } else { vsf_cmdio_write(p_sess, FTP_MDTMOK, "File modification time set."); } } } else { if (retval != 0 || !vsf_sysutil_statbuf_is_regfile(s_p_statbuf)) { vsf_cmdio_write(p_sess, FTP_FILEFAIL, "Could not get file modification time."); } else { static struct mystr s_mdtm_res_str; str_alloc_text(&s_mdtm_res_str, vsf_sysutil_statbuf_get_numeric_date( s_p_statbuf, tunable_use_localtime)); vsf_cmdio_write_str(p_sess, FTP_MDTMOK, &s_mdtm_res_str); } } } static void handle_eprt(struct vsf_session* p_sess) { static struct mystr s_part1_str; static struct mystr s_part2_str; static struct mystr s_scopeid_str; int proto; int port; const unsigned char* p_raw_addr; int is_ipv6 = vsf_sysutil_sockaddr_is_ipv6(p_sess->p_local_addr); port_cleanup(p_sess); pasv_cleanup(p_sess); str_copy(&s_part1_str, &p_sess->ftp_arg_str); str_split_char(&s_part1_str, &s_part2_str, '|'); if (!str_isempty(&s_part1_str)) { goto bad_eprt; } /* Split out the protocol and check it */ str_split_char(&s_part2_str, &s_part1_str, '|'); proto = str_atoi(&s_part2_str); if (proto < 1 || proto > 2 || (!is_ipv6 && proto == 2)) { vsf_cmdio_write(p_sess, FTP_BADCMD, "Bad EPRT protocol."); return; } /* Split out address and parse it */ str_split_char(&s_part1_str, &s_part2_str, '|'); if (proto == 2) { str_split_char(&s_part1_str, &s_scopeid_str, '%'); p_raw_addr = vsf_sysutil_parse_ipv6(&s_part1_str); } else { p_raw_addr = vsf_sysutil_parse_ipv4(&s_part1_str); } if (!p_raw_addr) { goto bad_eprt; } /* Split out port and parse it */ str_split_char(&s_part2_str, &s_part1_str, '|'); if (!str_isempty(&s_part1_str) || str_isempty(&s_part2_str)) { goto bad_eprt; } port = str_atoi(&s_part2_str); if (port < 0 || port > 65535) { goto bad_eprt; } vsf_sysutil_sockaddr_clone(&p_sess->p_port_sockaddr, p_sess->p_local_addr); if (proto == 2) { vsf_sysutil_sockaddr_set_ipv6addr(p_sess->p_port_sockaddr, p_raw_addr); } else { vsf_sysutil_sockaddr_set_ipv4addr(p_sess->p_port_sockaddr, p_raw_addr); } vsf_sysutil_sockaddr_set_port(p_sess->p_port_sockaddr, (unsigned short) port); /* SECURITY: * 1) Reject requests not connecting to the control socket IP * 2) Reject connects to privileged ports */ if (!tunable_port_promiscuous) { if (!vsf_sysutil_sockaddr_addr_equal(p_sess->p_remote_addr, p_sess->p_port_sockaddr) || vsf_sysutil_is_port_reserved((unsigned short) port)) { vsf_cmdio_write(p_sess, FTP_BADCMD, "Illegal EPRT command."); port_cleanup(p_sess); return; } } vsf_cmdio_write(p_sess, FTP_EPRTOK, "EPRT command successful. Consider using EPSV."); return; bad_eprt: vsf_cmdio_write(p_sess, FTP_BADCMD, "Bad EPRT command."); } /* XXX - add AUTH etc. */ static void handle_help(struct vsf_session* p_sess) { vsf_cmdio_write_hyphen(p_sess, FTP_HELP, "The following commands are recognized."); vsf_cmdio_write_raw(p_sess, " ABOR ACCT ALLO APPE CDUP CWD DELE EPRT EPSV FEAT HELP LIST MDTM MKD\r\n"); vsf_cmdio_write_raw(p_sess, " MODE NLST NOOP OPTS PASS PASV PORT PWD QUIT REIN REST RETR RMD RNFR\r\n"); vsf_cmdio_write_raw(p_sess, " RNTO SITE SIZE SMNT STAT STOR STOU STRU SYST TYPE USER XCUP XCWD XMKD\r\n"); vsf_cmdio_write_raw(p_sess, " XPWD XRMD\r\n"); vsf_cmdio_write(p_sess, FTP_HELP, "Help OK."); } static void handle_stou(struct vsf_session* p_sess) { handle_upload_common(p_sess, 0, 1); } static void get_unique_filename(struct mystr* p_outstr, const struct mystr* p_base_str) { /* Use silly wu-ftpd algorithm for compatibility. It has races of course, if * two sessions are using the same file prefix at the same time. */ static struct vsf_sysutil_statbuf* s_p_statbuf; static struct mystr s_stou_str; unsigned int suffix = 1; const struct mystr* p_real_base_str = p_base_str; int retval; if (str_isempty(p_real_base_str)) { str_alloc_text(&s_stou_str, "STOU"); p_real_base_str = &s_stou_str; } else { /* Do not add any suffix at all if the name is not taken. */ retval = str_stat(p_real_base_str, &s_p_statbuf); if (vsf_sysutil_retval_is_error(retval)) { str_copy(p_outstr, p_real_base_str); return; } } while (1) { str_copy(p_outstr, p_real_base_str); str_append_char(p_outstr, '.'); str_append_ulong(p_outstr, suffix); retval = str_stat(p_outstr, &s_p_statbuf); if (vsf_sysutil_retval_is_error(retval)) { return; } ++suffix; } } static void handle_stat(struct vsf_session* p_sess) { vsf_cmdio_write_hyphen(p_sess, FTP_STATOK, "FTP server status:"); vsf_cmdio_write_raw(p_sess, " Connected to "); vsf_cmdio_write_raw(p_sess, str_getbuf(&p_sess->remote_ip_str)); vsf_cmdio_write_raw(p_sess, "\r\n"); vsf_cmdio_write_raw(p_sess, " Logged in as "); vsf_cmdio_write_raw(p_sess, str_getbuf(&p_sess->user_str)); vsf_cmdio_write_raw(p_sess, "\r\n"); vsf_cmdio_write_raw(p_sess, " TYPE: "); if (p_sess->is_ascii) { vsf_cmdio_write_raw(p_sess, "ASCII\r\n"); } else { vsf_cmdio_write_raw(p_sess, "BINARY\r\n"); } if (p_sess->bw_rate_max == 0) { vsf_cmdio_write_raw(p_sess, " No session bandwidth limit\r\n"); } else { vsf_cmdio_write_raw(p_sess, " Session bandwidth limit in byte/s is "); vsf_cmdio_write_raw(p_sess, vsf_sysutil_ulong_to_str(p_sess->bw_rate_max)); vsf_cmdio_write_raw(p_sess, "\r\n"); } if (tunable_idle_session_timeout == 0) { vsf_cmdio_write_raw(p_sess, " No session timeout\r\n"); } else { vsf_cmdio_write_raw(p_sess, " Session timeout in seconds is "); vsf_cmdio_write_raw(p_sess, vsf_sysutil_ulong_to_str(tunable_idle_session_timeout)); vsf_cmdio_write_raw(p_sess, "\r\n"); } if (p_sess->control_use_ssl) { vsf_cmdio_write_raw(p_sess, " Control connection is encrypted\r\n"); } else { vsf_cmdio_write_raw(p_sess, " Control connection is plain text\r\n"); } if (p_sess->data_use_ssl) { vsf_cmdio_write_raw(p_sess, " Data connections will be encrypted\r\n"); } else { vsf_cmdio_write_raw(p_sess, " Data connections will be plain text\r\n"); } if (p_sess->num_clients > 0) { vsf_cmdio_write_raw(p_sess, " At session startup, client count was "); vsf_cmdio_write_raw(p_sess, vsf_sysutil_ulong_to_str(p_sess->num_clients)); vsf_cmdio_write_raw(p_sess, "\r\n"); } vsf_cmdio_write_raw(p_sess, " vsFTPd " VSF_VERSION " - secure, fast, stable\r\n"); vsf_cmdio_write(p_sess, FTP_STATOK, "End of status"); } static void handle_stat_file(struct vsf_session* p_sess) { handle_dir_common(p_sess, 1, 1); } static int data_transfer_checks_ok(struct vsf_session* p_sess) { if (!pasv_active(p_sess) && !port_active(p_sess)) { vsf_cmdio_write(p_sess, FTP_BADSENDCONN, "Use PORT or PASV first."); return 0; } if (tunable_ssl_enable && !p_sess->data_use_ssl && ((tunable_force_local_data_ssl && !p_sess->is_anonymous) || (tunable_force_anon_data_ssl && p_sess->is_anonymous))) { vsf_cmdio_write( p_sess, FTP_NEEDENCRYPT, "Data connections must be encrypted."); return 0; } return 1; } static void resolve_tilde(struct mystr* p_str, struct vsf_session* p_sess) { unsigned int len = str_getlen(p_str); if (len > 0 && str_get_char_at(p_str, 0) == '~') { static struct mystr s_rhs_str; if (len == 1 || str_get_char_at(p_str, 1) == '/') { str_split_char(p_str, &s_rhs_str, '~'); str_copy(p_str, &p_sess->home_str); str_append_str(p_str, &s_rhs_str); } else if (tunable_tilde_user_enable && len > 1) { static struct mystr s_user_str; struct vsf_sysutil_user* p_user; str_copy(&s_rhs_str, p_str); str_split_char(&s_rhs_str, &s_user_str, '~'); str_split_char(&s_user_str, &s_rhs_str, '/'); p_user = str_getpwnam(&s_user_str); if (p_user != 0) { str_alloc_text(p_str, vsf_sysutil_user_get_homedir(p_user)); if (!str_isempty(&s_rhs_str)) { str_append_char(p_str, '/'); str_append_str(p_str, &s_rhs_str); } } } } } static void handle_logged_in_user(struct vsf_session* p_sess) { if (p_sess->is_anonymous) { vsf_cmdio_write(p_sess, FTP_LOGINERR, "Can't change from guest user."); } else if (str_equal(&p_sess->user_str, &p_sess->ftp_arg_str)) { vsf_cmdio_write(p_sess, FTP_GIVEPWORD, "Any password will do."); } else { vsf_cmdio_write(p_sess, FTP_LOGINERR, "Can't change to another user."); } } static void handle_logged_in_pass(struct vsf_session* p_sess) { vsf_cmdio_write(p_sess, FTP_LOGINOK, "Already logged in."); } static void handle_http(struct vsf_session* p_sess) { /* Warning: Doesn't respect cmds_allowed etc. because there is currently only * one command (GET)! * HTTP likely doesn't respect other important FTP options. I don't think * logging works. */ if (!tunable_download_enable) { bug("HTTP needs download - fix your config"); } /* Eat the HTTP headers, which we don't care about. */ do { vsf_cmdio_get_cmd_and_arg(p_sess, &p_sess->ftp_cmd_str, &p_sess->ftp_arg_str, 1); } while (!str_isempty(&p_sess->ftp_cmd_str) || !str_isempty(&p_sess->ftp_arg_str)); vsf_cmdio_write_raw(p_sess, "HTTP/1.1 200 OK\r\n"); vsf_cmdio_write_raw(p_sess, "Server: vsftpd\r\n"); vsf_cmdio_write_raw(p_sess, "Connection: close\r\n"); vsf_cmdio_write_raw(p_sess, "X-Frame-Options: SAMEORIGIN\r\n"); vsf_cmdio_write_raw(p_sess, "X-Content-Type-Options: nosniff\r\n"); /* Split the path from the HTTP/1.x */ str_split_char(&p_sess->http_get_arg, &p_sess->ftp_arg_str, ' '); str_copy(&p_sess->ftp_arg_str, &p_sess->http_get_arg); str_split_char(&p_sess->http_get_arg, &p_sess->ftp_cmd_str, '.'); str_upper(&p_sess->ftp_cmd_str); if (str_equal_text(&p_sess->ftp_cmd_str, "HTML") || str_equal_text(&p_sess->ftp_cmd_str, "HTM")) { vsf_cmdio_write_raw(p_sess, "Content-Type: text/html\r\n"); } else { vsf_cmdio_write_raw(p_sess, "Content-Type: dunno\r\n"); } vsf_cmdio_write_raw(p_sess, "\r\n"); p_sess->is_ascii = 0; p_sess->restart_pos = 0; handle_retr(p_sess, 1); if (vsf_log_entry_pending(p_sess)) { vsf_log_do_log(p_sess, 0); } vsf_sysutil_exit(0); } vsftpd-3.0.3/oneprocess.c0000644000175000017500000001017412025247441014355 0ustar chrischris/* * Part of Very Secure FTPd * Licence: GPL v2 * Author: Chris Evans * oneprocess.c * * Code for the "one process" security model. The one process security model * is born for the purposes of raw speed at the expense of compromising the * purity of the security model. * The one process model will typically be disabled, for security reasons. * Only sites with huge numbers of concurrent users are likely to feel the * pain of two processes per session. */ #include "prelogin.h" #include "postlogin.h" #include "privops.h" #include "session.h" #include "secutil.h" #include "str.h" #include "tunables.h" #include "utility.h" #include "sysstr.h" #include "sysdeputil.h" #include "sysutil.h" #include "ptracesandbox.h" #include "ftppolicy.h" #include "seccompsandbox.h" static void one_process_start(void* p_arg); void vsf_one_process_start(struct vsf_session* p_sess) { if (tunable_ptrace_sandbox) { struct pt_sandbox* p_sandbox = ptrace_sandbox_alloc(); if (p_sandbox == 0) { die("could not allocate sandbox (only works for 32-bit builds)"); } policy_setup(p_sandbox, p_sess); if (ptrace_sandbox_launch_process(p_sandbox, one_process_start, (void*) p_sess) <= 0) { die("could not launch sandboxed child"); } /* TODO - could drop privs here. For now, run as root as the attack surface * is negligible, and running as root permits us to correctly deliver the * parent death signal upon unexpected crash. */ (void) ptrace_sandbox_run_processes(p_sandbox); ptrace_sandbox_free(p_sandbox); vsf_sysutil_exit(0); } else { one_process_start((void*) p_sess); } } static void one_process_start(void* p_arg) { struct vsf_session* p_sess = (struct vsf_session*) p_arg; unsigned int caps = 0; if (tunable_chown_uploads) { caps |= kCapabilityCAP_CHOWN; } if (tunable_connect_from_port_20) { caps |= kCapabilityCAP_NET_BIND_SERVICE; } { struct mystr user_name = INIT_MYSTR; struct mystr chdir_str = INIT_MYSTR; if (tunable_ftp_username) { str_alloc_text(&user_name, tunable_ftp_username); } if (tunable_anon_root) { str_alloc_text(&chdir_str, tunable_anon_root); } if (tunable_run_as_launching_user) { if (!str_isempty(&chdir_str)) { str_chdir(&chdir_str); } } else { vsf_secutil_change_credentials(&user_name, 0, &chdir_str, caps, VSF_SECUTIL_OPTION_CHROOT | VSF_SECUTIL_OPTION_USE_GROUPS | VSF_SECUTIL_OPTION_NO_PROCS); } str_free(&user_name); str_free(&chdir_str); } if (tunable_ptrace_sandbox) { ptrace_sandbox_attach_point(); } seccomp_sandbox_init(); seccomp_sandbox_setup_postlogin(p_sess); seccomp_sandbox_lockdown(); init_connection(p_sess); } void vsf_one_process_login(struct vsf_session* p_sess, const struct mystr* p_pass_str) { enum EVSFPrivopLoginResult login_result = vsf_privop_do_login(p_sess, p_pass_str); switch (login_result) { case kVSFLoginFail: return; break; case kVSFLoginAnon: p_sess->is_anonymous = 1; process_post_login(p_sess); break; case kVSFLoginNull: /* Fall through. */ case kVSFLoginReal: /* Fall through. */ default: bug("bad state in vsf_one_process_login"); break; } } int vsf_one_process_get_priv_data_sock(struct vsf_session* p_sess) { unsigned short port = vsf_sysutil_sockaddr_get_port(p_sess->p_port_sockaddr); return vsf_privop_get_ftp_port_sock(p_sess, port, 1); } void vsf_one_process_pasv_cleanup(struct vsf_session* p_sess) { vsf_privop_pasv_cleanup(p_sess); } int vsf_one_process_pasv_active(struct vsf_session* p_sess) { return vsf_privop_pasv_active(p_sess); } unsigned short vsf_one_process_listen(struct vsf_session* p_sess) { return vsf_privop_pasv_listen(p_sess); } int vsf_one_process_get_pasv_fd(struct vsf_session* p_sess) { return vsf_privop_accept_pasv(p_sess); } void vsf_one_process_chown_upload(struct vsf_session* p_sess, int fd) { vsf_privop_do_file_chown(p_sess, fd); } vsftpd-3.0.3/filestr.c0000644000175000017500000000262712025276113013647 0ustar chrischris/* * Part of Very Secure FTPd * Licence: GPL v2 * Author: Chris Evans * filestr.c * * This file contains extensions to the string/buffer API, to load a file * into a buffer. */ #include "filestr.h" /* Get access to "private" functions */ #define VSFTP_STRING_HELPER #include "str.h" #include "sysutil.h" #include "secbuf.h" #include "utility.h" int str_fileread(struct mystr* p_str, const char* p_filename, unsigned int maxsize) { int fd; int retval = 0; filesize_t size; char* p_sec_buf = 0; struct vsf_sysutil_statbuf* p_stat = 0; /* In case we fail, make sure we return an empty string */ str_empty(p_str); fd = vsf_sysutil_open_file(p_filename, kVSFSysUtilOpenReadOnly); if (vsf_sysutil_retval_is_error(fd)) { return fd; } vsf_sysutil_fstat(fd, &p_stat); if (vsf_sysutil_statbuf_is_regfile(p_stat)) { size = vsf_sysutil_statbuf_get_size(p_stat); if (size > maxsize) { size = maxsize; } vsf_secbuf_alloc(&p_sec_buf, (unsigned int) size); retval = vsf_sysutil_read_loop(fd, p_sec_buf, (unsigned int) size); if (vsf_sysutil_retval_is_error(retval)) { goto free_out; } else if ((unsigned int) retval != size) { die("read size mismatch"); } str_alloc_memchunk(p_str, p_sec_buf, (unsigned int) size); } free_out: vsf_sysutil_free(p_stat); vsf_secbuf_free(&p_sec_buf); vsf_sysutil_close(fd); return retval; } vsftpd-3.0.3/hash.h0000644000175000017500000000072510750743701013131 0ustar chrischris#ifndef VSFTP_HASH_H #define VSFTP_HASH_H struct hash; typedef unsigned int (*hashfunc_t)(unsigned int, void*); struct hash* hash_alloc(unsigned int buckets, unsigned int key_size, unsigned int value_size, hashfunc_t hash_func); void* hash_lookup_entry(struct hash* p_hash, void* p_key); void hash_add_entry(struct hash* p_hash, void* p_key, void* p_value); void hash_free_entry(struct hash* p_hash, void* p_key); #endif /* VSFTP_HASH_H */ vsftpd-3.0.3/str.h0000644000175000017500000001160612025274540013013 0ustar chrischris#ifndef VSFTP_STR_H #define VSFTP_STR_H /* TODO - document these functions ;-) */ #ifndef VSF_FILESIZE_H #include "filesize.h" #endif struct mystr { char* PRIVATE_HANDS_OFF_p_buf; /* Internally, EXCLUDES trailing null */ unsigned int PRIVATE_HANDS_OFF_len; unsigned int PRIVATE_HANDS_OFF_alloc_bytes; }; #define INIT_MYSTR \ { (void*)0, 0, 0 } #ifdef VSFTP_STRING_HELPER #define str_alloc_memchunk private_str_alloc_memchunk #endif void private_str_alloc_memchunk(struct mystr* p_str, const char* p_src, unsigned int len); void str_alloc_text(struct mystr* p_str, const char* p_src); /* NOTE: String buffer data does NOT include terminating character */ void str_alloc_alt_term(struct mystr* p_str, const char* p_src, char term); void str_alloc_ulong(struct mystr* p_str, unsigned long the_ulong); void str_alloc_filesize_t(struct mystr* p_str, filesize_t the_filesize); void str_copy(struct mystr* p_dest, const struct mystr* p_src); const char* str_strdup(const struct mystr* p_str); void str_empty(struct mystr* p_str); void str_free(struct mystr* p_str); void str_trunc(struct mystr* p_str, unsigned int trunc_len); void str_reserve(struct mystr* p_str, unsigned int res_len); int str_isempty(const struct mystr* p_str); unsigned int str_getlen(const struct mystr* p_str); const char* str_getbuf(const struct mystr* p_str); int str_strcmp(const struct mystr* p_str1, const struct mystr* p_str2); int str_equal(const struct mystr* p_str1, const struct mystr* p_str2); int str_equal_text(const struct mystr* p_str, const char* p_text); void str_append_str(struct mystr* p_str, const struct mystr* p_other); void str_append_text(struct mystr* p_str, const char* p_src); void str_append_ulong(struct mystr* p_str, unsigned long the_long); void str_append_filesize_t(struct mystr* p_str, filesize_t the_filesize); void str_append_char(struct mystr* p_str, char the_char); void str_append_double(struct mystr* p_str, double the_double); void str_upper(struct mystr* p_str); void str_rpad(struct mystr* p_str, const unsigned int min_width); void str_lpad(struct mystr* p_str, const unsigned int min_width); void str_replace_char(struct mystr* p_str, char from, char to); void str_replace_text(struct mystr* p_str, const char* p_from, const char* p_to); void str_split_char(struct mystr* p_src, struct mystr* p_rhs, char c); void str_split_char_reverse(struct mystr* p_src, struct mystr* p_rhs, char c); void str_split_text(struct mystr* p_src, struct mystr* p_rhs, const char* p_text); void str_split_text_reverse(struct mystr* p_src, struct mystr* p_rhs, const char* p_text); struct str_locate_result { int found; unsigned int index; char char_found; }; struct str_locate_result str_locate_char( const struct mystr* p_str, char look_char); struct str_locate_result str_locate_str( const struct mystr* p_str, const struct mystr* p_look_str); struct str_locate_result str_locate_str_reverse( const struct mystr* p_str, const struct mystr* p_look_str); struct str_locate_result str_locate_text( const struct mystr* p_str, const char* p_text); struct str_locate_result str_locate_text_reverse( const struct mystr* p_str, const char* p_text); struct str_locate_result str_locate_chars( const struct mystr* p_str, const char* p_chars); void str_left(const struct mystr* p_str, struct mystr* p_out, unsigned int chars); void str_right(const struct mystr* p_str, struct mystr* p_out, unsigned int chars); void str_mid_to_end(const struct mystr* p_str, struct mystr* p_out, unsigned int indexx); char str_get_char_at(const struct mystr* p_str, const unsigned int indexx); int str_contains_space(const struct mystr* p_str); int str_all_space(const struct mystr* p_str); int str_contains_unprintable(const struct mystr* p_str); void str_replace_unprintable(struct mystr* p_str, char new_char); int str_atoi(const struct mystr* p_str); filesize_t str_a_to_filesize_t(const struct mystr* p_str); unsigned int str_octal_to_uint(const struct mystr* p_str); /* PURPOSE: Extract a line of text (delimited by \n or EOF) from a string * buffer, starting at character position 'p_pos'. The extracted line will * not contain the '\n' terminator. * * RETURNS: 0 if no more lines are available, 1 otherwise. * The extracted text line is stored in 'p_line_str', which is * emptied if there are no more lines. 'p_pos' is updated to point to the * first character after the end of the line just extracted. */ int str_getline(const struct mystr* p_str, struct mystr* p_line_str, unsigned int* p_pos); /* PURPOSE: Detect whether or not a string buffer contains a specific line * of text (delimited by \n or EOF). * * RETURNS: 1 if there is a matching line, 0 otherwise. */ int str_contains_line(const struct mystr* p_str, const struct mystr* p_line_str); #endif /* VSFTP_STR_H */ vsftpd-3.0.3/parseconf.h0000644000175000017500000000220611227153641014160 0ustar chrischris#ifndef VSF_PARSECONF_H #define VSF_PARSECONF_H /* vsf_parseconf_load_file() * PURPOSE * Parse the given file as a vsftpd config file. If the file cannot be * opened for whatever reason, a fatal error is raised. If the file contains * any syntax errors, a fatal error is raised. * If the call returns (no fatal error raised), then the config file was * parsed and the global config settings will have been updated. * PARAMETERS * p_filename - the name of the config file to parse * errs_fatal - errors will cause the calling process to exit if not 0 * NOTES * If p_filename is NULL, then the last filename passed to this function is * used to reload the configuration details. */ void vsf_parseconf_load_file(const char* p_filename, int errs_fatal); /* vsf_parseconf_parse_setting() * PURPOSE * Handle a given name=value setting and apply it. Any whitespace at the * beginning is skipped. * PARAMETERS * p_settings - the name=value pair to apply * errs_fatal - errors will cause the calling process to exit if not 0 */ void vsf_parseconf_load_setting(const char* p_setting, int errs_fatal); #endif /* VSF_PARSECONF_H */ vsftpd-3.0.3/vsf_findlibs.sh0000755000175000017500000000462111734472305015045 0ustar chrischris#!/bin/sh # Cheesy hacky location of additional link libraries. locate_library() { [ ! "$1*" = "`echo $1*`" ]; } find_func() { egrep $1 $2 >/dev/null; } if find_func hosts_access tcpwrap.o; then echo "-lwrap"; locate_library /lib/libnsl.so && echo "-lnsl"; locate_library /lib64/libnsl.so && echo "-lnsl"; fi # Look for PAM (done weirdly due to distribution bugs (e.g. Debian) or the # crypt library. if find_func pam_start sysdeputil.o; then locate_library /lib/libpam.so.0 && echo "/lib/libpam.so.0"; locate_library /usr/lib/libpam.so && echo "-lpam"; locate_library /usr/lib64/libpam.so && echo "-lpam"; locate_library /lib/x86_64-linux-gnu/libpam.so.0 && echo "-lpam"; # HP-UX ends shared libraries with .sl locate_library /usr/lib/libpam.sl && echo "-lpam"; # AIX ends shared libraries with .a locate_library /usr/lib/libpam.a && echo "-lpam"; else locate_library /lib/libcrypt.so && echo "-lcrypt"; locate_library /usr/lib/libcrypt.so && echo "-lcrypt"; locate_library /usr/lib64/libcrypt.so && echo "-lcrypt"; locate_library /lib/x86_64-linux-gnu/libcrypt.so && echo "-lcrypt"; fi # Look for the dynamic linker library. Needed by older RedHat when # you link in PAM locate_library /lib/libdl.so && echo "-ldl"; # Look for libsocket. Solaris needs this. locate_library /lib/libsocket.so && echo "-lsocket"; # Look for libnsl. Solaris needs this. locate_library /lib/libnsl.so && echo "-lnsl"; # Look for libresolv. Solaris needs this. locate_library /lib/libresolv.so && echo "-lresolv"; # Look for libutil. Older FreeBSD need this for setproctitle(). locate_library /usr/lib/libutil.so && echo "-lutil"; # For older HP-UX... locate_library /usr/lib/libsec.sl && echo "-lsec"; # Look for libcap (capabilities) if locate_library /lib/libcap.so.1; then echo "/lib/libcap.so.1"; elif locate_library /lib/libcap.so.2; then echo "/lib/libcap.so.2"; else locate_library /usr/lib/libcap.so && echo "-lcap"; locate_library /lib/libcap.so && echo "-lcap"; locate_library /lib64/libcap.so && echo "-lcap"; fi # Solaris needs this for nanosleep().. locate_library /lib/libposix4.so && echo "-lposix4"; locate_library /usr/lib/libposix4.so && echo "-lposix4"; # Tru64 (nanosleep) locate_library /usr/shlib/librt.so && echo "-lrt"; # Solaris sendfile locate_library /usr/lib/libsendfile.so && echo "-lsendfile"; # OpenSSL if find_func SSL_library_init ssl.o; then echo "-lssl -lcrypto"; fi exit 0; vsftpd-3.0.3/opts.h0000644000175000017500000000020410754214151013157 0ustar chrischris#ifndef VSF_OPTS_H #define VSF_OPTS_H struct vsf_session; void handle_opts(struct vsf_session* p_sess); #endif /* VSF_OPTS_H */ vsftpd-3.0.3/sysutil.h0000644000175000017500000003502612025266771013730 0ustar chrischris#ifndef VSF_SYSUTIL_H #define VSF_SYSUTIL_H /* TODO: these functions need proper documenting! */ #ifndef VSF_FILESIZE_H #include "filesize.h" #endif /* Return value queries */ int vsf_sysutil_retval_is_error(int retval); enum EVSFSysUtilError { kVSFSysUtilErrUnknown = 1, kVSFSysUtilErrADDRINUSE, kVSFSysUtilErrNOSYS, kVSFSysUtilErrINTR, kVSFSysUtilErrINVAL, kVSFSysUtilErrOPNOTSUPP, kVSFSysUtilErrACCES, kVSFSysUtilErrNOENT }; enum EVSFSysUtilError vsf_sysutil_get_error(void); /* Signal handling utility functions */ enum EVSFSysUtilSignal { kVSFSysUtilSigALRM = 1, kVSFSysUtilSigTERM, kVSFSysUtilSigCHLD, kVSFSysUtilSigPIPE, kVSFSysUtilSigURG, kVSFSysUtilSigHUP }; enum EVSFSysUtilInterruptContext { kVSFSysUtilUnknown, kVSFSysUtilIO }; typedef void (*vsf_sighandle_t)(void*); typedef void (*vsf_async_sighandle_t)(int); typedef void (*vsf_context_io_t)(int, int, void*); void vsf_sysutil_install_null_sighandler(const enum EVSFSysUtilSignal sig); void vsf_sysutil_install_sighandler(const enum EVSFSysUtilSignal, vsf_sighandle_t handler, void* p_private, int use_alarm); void vsf_sysutil_install_async_sighandler(const enum EVSFSysUtilSignal sig, vsf_async_sighandle_t handler); void vsf_sysutil_default_sig(const enum EVSFSysUtilSignal sig); void vsf_sysutil_install_io_handler(vsf_context_io_t handler, void* p_private); void vsf_sysutil_uninstall_io_handler(void); void vsf_sysutil_check_pending_actions( const enum EVSFSysUtilInterruptContext context, int retval, int fd); void vsf_sysutil_block_sig(const enum EVSFSysUtilSignal sig); void vsf_sysutil_unblock_sig(const enum EVSFSysUtilSignal sig); /* Alarm setting/clearing utility functions */ void vsf_sysutil_set_alarm(const unsigned int trigger_seconds); void vsf_sysutil_clear_alarm(void); /* Directory related things */ char* vsf_sysutil_getcwd(char* p_dest, const unsigned int buf_size); int vsf_sysutil_mkdir(const char* p_dirname, const unsigned int mode); int vsf_sysutil_rmdir(const char* p_dirname); int vsf_sysutil_chdir(const char* p_dirname); int vsf_sysutil_rename(const char* p_from, const char* p_to); struct vsf_sysutil_dir; struct vsf_sysutil_dir* vsf_sysutil_opendir(const char* p_dirname); void vsf_sysutil_closedir(struct vsf_sysutil_dir* p_dir); const char* vsf_sysutil_next_dirent(struct vsf_sysutil_dir* p_dir); /* File create/open/close etc. */ enum EVSFSysUtilOpenMode { kVSFSysUtilOpenReadOnly = 1, kVSFSysUtilOpenWriteOnly, kVSFSysUtilOpenReadWrite }; int vsf_sysutil_open_file(const char* p_filename, const enum EVSFSysUtilOpenMode); /* Fails if file already exists */ int vsf_sysutil_create_file_exclusive(const char* p_filename); /* Creates file or appends if already exists */ int vsf_sysutil_create_or_open_file_append(const char* p_filename, unsigned int mode); /* Creates or appends */ int vsf_sysutil_create_or_open_file(const char* p_filename, unsigned int mode); void vsf_sysutil_dupfd2(int old_fd, int new_fd); void vsf_sysutil_close(int fd); int vsf_sysutil_close_failok(int fd); int vsf_sysutil_unlink(const char* p_dead); int vsf_sysutil_write_access(const char* p_filename); void vsf_sysutil_ftruncate(int fd); /* Reading and writing */ void vsf_sysutil_lseek_to(const int fd, filesize_t seek_pos); void vsf_sysutil_lseek_end(const int fd); filesize_t vsf_sysutil_get_file_offset(const int file_fd); int vsf_sysutil_read(const int fd, void* p_buf, const unsigned int size); int vsf_sysutil_write(const int fd, const void* p_buf, const unsigned int size); /* Reading and writing, with handling of interrupted system calls and partial * reads/writes. Slightly more usable than the standard UNIX API! */ int vsf_sysutil_read_loop(const int fd, void* p_buf, unsigned int size); int vsf_sysutil_write_loop(const int fd, const void* p_buf, unsigned int size); struct vsf_sysutil_statbuf; int vsf_sysutil_stat(const char* p_name, struct vsf_sysutil_statbuf** p_ptr); int vsf_sysutil_lstat(const char* p_name, struct vsf_sysutil_statbuf** p_ptr); void vsf_sysutil_fstat(int fd, struct vsf_sysutil_statbuf** p_ptr); void vsf_sysutil_dir_stat(const struct vsf_sysutil_dir* p_dir, struct vsf_sysutil_statbuf** p_ptr); int vsf_sysutil_statbuf_is_regfile(const struct vsf_sysutil_statbuf* p_stat); int vsf_sysutil_statbuf_is_symlink(const struct vsf_sysutil_statbuf* p_stat); int vsf_sysutil_statbuf_is_socket(const struct vsf_sysutil_statbuf* p_stat); int vsf_sysutil_statbuf_is_dir(const struct vsf_sysutil_statbuf* p_stat); filesize_t vsf_sysutil_statbuf_get_size( const struct vsf_sysutil_statbuf* p_stat); const char* vsf_sysutil_statbuf_get_perms( const struct vsf_sysutil_statbuf* p_stat); const char* vsf_sysutil_statbuf_get_date( const struct vsf_sysutil_statbuf* p_stat, int use_localtime, long curr_time); const char* vsf_sysutil_statbuf_get_numeric_date( const struct vsf_sysutil_statbuf* p_stat, int use_localtime); unsigned int vsf_sysutil_statbuf_get_links( const struct vsf_sysutil_statbuf* p_stat); int vsf_sysutil_statbuf_get_uid(const struct vsf_sysutil_statbuf* p_stat); int vsf_sysutil_statbuf_get_gid(const struct vsf_sysutil_statbuf* p_stat); int vsf_sysutil_statbuf_is_readable_other( const struct vsf_sysutil_statbuf* p_stat); const char* vsf_sysutil_statbuf_get_sortkey_mtime( const struct vsf_sysutil_statbuf* p_stat); int vsf_sysutil_chmod(const char* p_filename, unsigned int mode); void vsf_sysutil_fchown(const int fd, const int uid, const int gid); void vsf_sysutil_fchmod(const int fd, unsigned int mode); int vsf_sysutil_readlink(const char* p_filename, char* p_dest, unsigned int bufsiz); /* Get / unget various locks. Lock gets are blocking. Write locks are * exclusive; read locks are shared. */ int vsf_sysutil_lock_file_write(int fd); int vsf_sysutil_lock_file_read(int fd); void vsf_sysutil_unlock_file(int fd); /* Mapping/unmapping */ enum EVSFSysUtilMapPermission { kVSFSysUtilMapProtReadOnly = 1, kVSFSysUtilMapProtNone }; void vsf_sysutil_memprotect(void* p_addr, unsigned int len, const enum EVSFSysUtilMapPermission perm); void vsf_sysutil_memunmap(void* p_start, unsigned int length); /* Memory allocating/freeing */ void* vsf_sysutil_malloc(unsigned int size); void* vsf_sysutil_realloc(void* p_ptr, unsigned int size); void vsf_sysutil_free(void* p_ptr); /* Process creation/exit/process handling */ unsigned int vsf_sysutil_getpid(void); void vsf_sysutil_post_fork(void); int vsf_sysutil_fork(void); int vsf_sysutil_fork_failok(void); void vsf_sysutil_exit(int exit_code); struct vsf_sysutil_wait_retval { int PRIVATE_HANDS_OFF_syscall_retval; int PRIVATE_HANDS_OFF_exit_status; }; struct vsf_sysutil_wait_retval vsf_sysutil_wait(void); int vsf_sysutil_wait_reap_one(void); int vsf_sysutil_wait_get_retval( const struct vsf_sysutil_wait_retval* p_waitret); int vsf_sysutil_wait_exited_normally( const struct vsf_sysutil_wait_retval* p_waitret); int vsf_sysutil_wait_get_exitcode( const struct vsf_sysutil_wait_retval* p_waitret); /* Various string functions */ unsigned int vsf_sysutil_strlen(const char* p_text); char* vsf_sysutil_strdup(const char* p_str); void vsf_sysutil_memclr(void* p_dest, unsigned int size); void vsf_sysutil_memcpy(void* p_dest, const void* p_src, const unsigned int size); void vsf_sysutil_strcpy(char* p_dest, const char* p_src, unsigned int maxsize); int vsf_sysutil_memcmp(const void* p_src1, const void* p_src2, unsigned int size); int vsf_sysutil_strcmp(const char* p_src1, const char* p_src2); int vsf_sysutil_atoi(const char* p_str); filesize_t vsf_sysutil_a_to_filesize_t(const char* p_str); const char* vsf_sysutil_ulong_to_str(unsigned long the_ulong); const char* vsf_sysutil_filesize_t_to_str(filesize_t the_filesize); const char* vsf_sysutil_double_to_str(double the_double); const char* vsf_sysutil_uint_to_octal(unsigned int the_uint); unsigned int vsf_sysutil_octal_to_uint(const char* p_str); int vsf_sysutil_toupper(int the_char); int vsf_sysutil_isspace(int the_char); int vsf_sysutil_isprint(int the_char); int vsf_sysutil_isalnum(int the_char); int vsf_sysutil_isdigit(int the_char); /* Socket handling */ struct vsf_sysutil_sockaddr; struct vsf_sysutil_socketpair_retval { int socket_one; int socket_two; }; void vsf_sysutil_sockaddr_alloc(struct vsf_sysutil_sockaddr** p_sockptr); void vsf_sysutil_sockaddr_clear(struct vsf_sysutil_sockaddr** p_sockptr); void vsf_sysutil_sockaddr_alloc_ipv4(struct vsf_sysutil_sockaddr** p_sockptr); void vsf_sysutil_sockaddr_alloc_ipv6(struct vsf_sysutil_sockaddr** p_sockptr); void vsf_sysutil_sockaddr_clone( struct vsf_sysutil_sockaddr** p_sockptr, const struct vsf_sysutil_sockaddr* p_src); int vsf_sysutil_sockaddr_addr_equal(const struct vsf_sysutil_sockaddr* p1, const struct vsf_sysutil_sockaddr* p2); int vsf_sysutil_sockaddr_is_ipv6( const struct vsf_sysutil_sockaddr* p_sockaddr); void vsf_sysutil_sockaddr_set_ipv4addr(struct vsf_sysutil_sockaddr* p_sockptr, const unsigned char* p_raw); void vsf_sysutil_sockaddr_set_ipv6addr(struct vsf_sysutil_sockaddr* p_sockptr, const unsigned char* p_raw); void vsf_sysutil_sockaddr_set_any(struct vsf_sysutil_sockaddr* p_sockaddr); unsigned short vsf_sysutil_sockaddr_get_port( const struct vsf_sysutil_sockaddr* p_sockptr); void vsf_sysutil_sockaddr_set_port(struct vsf_sysutil_sockaddr* p_sockptr, unsigned short the_port); int vsf_sysutil_is_port_reserved(unsigned short port); int vsf_sysutil_get_ipsock(const struct vsf_sysutil_sockaddr* p_sockaddr); unsigned int vsf_sysutil_get_ipaddr_size(void); void* vsf_sysutil_sockaddr_get_raw_addr( struct vsf_sysutil_sockaddr* p_sockaddr); const void* vsf_sysutil_sockaddr_ipv6_v4( const struct vsf_sysutil_sockaddr* p_sockaddr); const void* vsf_sysutil_sockaddr_ipv4_v6( const struct vsf_sysutil_sockaddr* p_sockaddr); int vsf_sysutil_get_ipv4_sock(void); int vsf_sysutil_get_ipv6_sock(void); struct vsf_sysutil_socketpair_retval vsf_sysutil_unix_stream_socketpair(void); int vsf_sysutil_bind(int fd, const struct vsf_sysutil_sockaddr* p_sockptr); int vsf_sysutil_listen(int fd, const unsigned int backlog); void vsf_sysutil_getsockname(int fd, struct vsf_sysutil_sockaddr** p_sockptr); void vsf_sysutil_getpeername(int fd, struct vsf_sysutil_sockaddr** p_sockptr); int vsf_sysutil_accept_timeout(int fd, struct vsf_sysutil_sockaddr* p_sockaddr, unsigned int wait_seconds); int vsf_sysutil_connect_timeout(int fd, const struct vsf_sysutil_sockaddr* p_sockaddr, unsigned int wait_seconds); void vsf_sysutil_dns_resolve(struct vsf_sysutil_sockaddr** p_sockptr, const char* p_name); /* Option setting on sockets */ void vsf_sysutil_activate_keepalive(int fd); void vsf_sysutil_set_iptos_throughput(int fd); void vsf_sysutil_activate_reuseaddr(int fd); void vsf_sysutil_set_nodelay(int fd); void vsf_sysutil_activate_sigurg(int fd); void vsf_sysutil_activate_oobinline(int fd); void vsf_sysutil_activate_linger(int fd); void vsf_sysutil_deactivate_linger_failok(int fd); void vsf_sysutil_activate_noblock(int fd); void vsf_sysutil_deactivate_noblock(int fd); /* This does SHUT_RDWR */ void vsf_sysutil_shutdown_failok(int fd); /* And this does SHUT_RD */ void vsf_sysutil_shutdown_read_failok(int fd); int vsf_sysutil_recv_peek(const int fd, void* p_buf, unsigned int len); const char* vsf_sysutil_inet_ntop( const struct vsf_sysutil_sockaddr* p_sockptr); const char* vsf_sysutil_inet_ntoa(const void* p_raw_addr); int vsf_sysutil_inet_aton( const char* p_text, struct vsf_sysutil_sockaddr* p_addr); /* User database queries etc. */ struct vsf_sysutil_user; struct vsf_sysutil_group; struct vsf_sysutil_user* vsf_sysutil_getpwuid(const int uid); struct vsf_sysutil_user* vsf_sysutil_getpwnam(const char* p_user); const char* vsf_sysutil_user_getname(const struct vsf_sysutil_user* p_user); const char* vsf_sysutil_user_get_homedir( const struct vsf_sysutil_user* p_user); int vsf_sysutil_user_getuid(const struct vsf_sysutil_user* p_user); int vsf_sysutil_user_getgid(const struct vsf_sysutil_user* p_user); struct vsf_sysutil_group* vsf_sysutil_getgrgid(const int gid); const char* vsf_sysutil_group_getname(const struct vsf_sysutil_group* p_group); /* More random things */ unsigned int vsf_sysutil_getpagesize(void); unsigned char vsf_sysutil_get_random_byte(void); unsigned int vsf_sysutil_get_umask(void); void vsf_sysutil_set_umask(unsigned int umask); void vsf_sysutil_make_session_leader(void); void vsf_sysutil_reopen_standard_fds(void); void vsf_sysutil_tzset(void); const char* vsf_sysutil_get_current_date(void); void vsf_sysutil_qsort(void* p_base, unsigned int num_elem, unsigned int elem_size, int (*p_compar)(const void *, const void *)); char* vsf_sysutil_getenv(const char* p_var); typedef void (*exitfunc_t)(void); void vsf_sysutil_set_exit_func(exitfunc_t exitfunc); int vsf_sysutil_getuid(void); /* Syslogging (bah) */ void vsf_sysutil_openlog(int force); void vsf_sysutil_syslog(const char* p_text, int severe); void vsf_sysutil_closelog(void); /* Credentials handling */ int vsf_sysutil_running_as_root(void); void vsf_sysutil_setuid(const struct vsf_sysutil_user* p_user); void vsf_sysutil_setgid(const struct vsf_sysutil_user* p_user); void vsf_sysutil_setuid_numeric(int uid); void vsf_sysutil_setgid_numeric(int gid); int vsf_sysutil_geteuid(void); int vsf_sysutil_getegid(void); void vsf_sysutil_seteuid(const struct vsf_sysutil_user* p_user); void vsf_sysutil_setegid(const struct vsf_sysutil_user* p_user); void vsf_sysutil_seteuid_numeric(int uid); void vsf_sysutil_setegid_numeric(int gid); void vsf_sysutil_clear_supp_groups(void); void vsf_sysutil_initgroups(const struct vsf_sysutil_user* p_user); void vsf_sysutil_chroot(const char* p_root_path); /* Time handling */ /* Do not call get_time_usec() without calling get_time_sec() * first otherwise you will get stale data. */ long vsf_sysutil_get_time_sec(void); long vsf_sysutil_get_time_usec(void); long vsf_sysutil_parse_time(const char* p_text); void vsf_sysutil_sleep(double seconds); int vsf_sysutil_setmodtime(const char* p_file, long the_time, int is_localtime); /* Limits */ void vsf_sysutil_set_address_space_limit(unsigned long bytes); void vsf_sysutil_set_no_fds(void); void vsf_sysutil_set_no_procs(void); #endif /* VSF_SYSUTIL_H */ vsftpd-3.0.3/COPYING0000644000175000017500000004356010750743701013074 0ustar chrischrisvsftpd is licensed under version 2 of the GNU GPL. As copyright holder, I give permission for vsftpd to be linked to the OpenSSL libraries. This includes permission for vsftpd binaries to be distributed linked against the OpenSSL libraries. All other obligations under the GPL v2 remain intact. GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 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 of the License, 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. vsftpd-3.0.3/twoprocess.h0000644000175000017500000000466511224705610014417 0ustar chrischris#ifndef VSF_TWOPROCESS_H #define VSF_TWOPROCESS_H struct mystr; struct vsf_session; /* vsf_two_process_start() * PURPOSE * Called to start FTP login processing using the two process model. This * launches the unprivileged child to process the FTP login. * PARAMETERS * p_sess - the current session object */ void vsf_two_process_start(struct vsf_session* p_sess); /* vsf_two_process_login() * PURPOSE * Called to propose a login using the two process model. * PARAMETERS * p_sess - the current session object * p_pass_str - the proposed password */ void vsf_two_process_login(struct vsf_session* p_sess, const struct mystr* p_pass_str); /* vsf_two_process_get_priv_data_sock() * PURPOSE * Get a privileged port 20 bound data socket using the two process model. * PARAMETERS * p_sess - the current session object * RETURNS * The file descriptor of the privileged socket */ int vsf_two_process_get_priv_data_sock(struct vsf_session* p_sess); /* vsf_two_process_pasv_cleanup() * PURPOSE * Clean up any listening passive socket in the privileged side. * PARAMETERS * p_sess - the current session object */ void vsf_two_process_pasv_cleanup(struct vsf_session* p_sess); /* vsf_two_process_pasv_active() * PURPOSE * Determine if the passive socket is listening on the privileged side. * PARAMETERS * p_sess - the current session object * RETURNS * 1 if active, 0 if not. */ int vsf_two_process_pasv_active(struct vsf_session* p_sess); /* vsf_two_process_listen() * PURPOSE * Start listening for an incoming connection on the passive socket in the * privileged side. * PARAMETERS * p_sess - the current session object * RETURNS * The port we listened on. */ unsigned short vsf_two_process_listen(struct vsf_session* p_sess); /* vsf_two_process_get_pasv_fd() * PURPOSE * Accept an incoming connection on the passive socket in the privileged * side. * PARAMETERS * p_sess - the current session object * RETURNS * The file descriptor for the incoming connection. */ int vsf_two_process_get_pasv_fd(struct vsf_session* p_sess); /* vsf_two_process_chown_upload() * PURPOSE * Change ownership of an uploaded file using the two process model. * PARAMETERS * p_sess - the current session object * fd - the file descriptor to change ownership on */ void vsf_two_process_chown_upload(struct vsf_session* p_sess, int fd); #endif /* VSF_TWOPROCESS_H */ vsftpd-3.0.3/logging.h0000644000175000017500000000444111043741761013633 0ustar chrischris#ifndef VSF_LOGGING_H #define VSF_LOGGING_H /* Forward delcarations */ struct mystr; struct vsf_session; enum EVSFLogEntryType { kVSFLogEntryNull = 1, kVSFLogEntryDownload, kVSFLogEntryUpload, kVSFLogEntryMkdir, kVSFLogEntryLogin, kVSFLogEntryFTPInput, kVSFLogEntryFTPOutput, kVSFLogEntryConnection, kVSFLogEntryDelete, kVSFLogEntryRename, kVSFLogEntryRmdir, kVSFLogEntryChmod, kVSFLogEntryDebug, }; /* vsf_log_init() * PURPOSE * Initialize the logging services, by opening a writable file descriptor to * the log file (should logging be enabled). * PARAMETERS * p_sess - the current session object */ void vsf_log_init(struct vsf_session* p_sess); /* vsf_log_start_entry() * PURPOSE * Denote the start of a logged operation. Importantly, timing information * (if applicable) will be taken starting from this call. * PARAMETERS * p_sess - the current session object * what - the type of operation which just started */ void vsf_log_start_entry(struct vsf_session* p_sess, enum EVSFLogEntryType what); /* vsf_log_entry_pending() * PURPOSE * Determine whether a log entry has been started and not yet closed. * RETURNS * 0 if no log entry is pending; 1 if one is. */ int vsf_log_entry_pending(struct vsf_session* p_sess); /* vsf_log_clear_entry() * PURPOSE * Clears any pending log entry. */ void vsf_log_clear_entry(struct vsf_session* p_sess); /* vsf_log_do_log() * PURPOSE * Denote the end of a logged operation, specifying whether the operation * was successful or not. * PARAMETERS * p_sess - the current session object * succeeded - 0 for a failed operation, 1 for a successful operation */ void vsf_log_do_log(struct vsf_session* p_sess, int succeeded); /* vsf_log_line() * PURPOSE * Logs a single line of information, without disturbing any pending log * operations (e.g. a download log spans a period of time). * This call must be used for any logging calls nested within a call to * the vsf_log_start_entry() function. * PARAMETERS * p_sess - the current session object * what - the type of operation to log * p_str - the string to log */ void vsf_log_line(struct vsf_session* p_sess, enum EVSFLogEntryType what, struct mystr* p_str); #endif /* VSF_LOGGING_H */ vsftpd-3.0.3/access.h0000644000175000017500000000140210750743677013454 0ustar chrischris#ifndef VSF_ACCESS_H #define VSF_ACCESS_H struct mystr; /* vsf_access_check_file() * PURPOSE * Check whether the current session has permission to access the given * filename. * PARAMETERS * p_filename_str - the filename to check access for * RETURNS * Returns 1 if access is granted, otherwise 0. */ int vsf_access_check_file(const struct mystr* p_filename_str); /* vsf_access_check_file_visible() * PURPOSE * Check whether the current session has permission to view the given * filename in directory listings. * PARAMETERS * p_filename_str - the filename to check visibility for * RETURNS * Returns 1 if the file should be visible, otherwise 0. */ int vsf_access_check_file_visible(const struct mystr* p_filename_str); #endif /* VSF_ACCESS_H */ vsftpd-3.0.3/features.h0000644000175000017500000000022010750743702014013 0ustar chrischris#ifndef VSF_FEATURES_H #define VSF_FEATURES_H struct vsf_session; void handle_feat(struct vsf_session* p_sess); #endif /* VSF_FEATURES_H */ vsftpd-3.0.3/ipaddrparse.c0000644000175000017500000001163110750743701014475 0ustar chrischris/* * Part of Very Secure FTPd * Licence: GPL v2 * Author: Chris Evans * ipaddrparse.c * * A routine to parse ip addresses. I'm paranoid and don't want to use * inet_pton. */ #include "ipaddrparse.h" #include "sysutil.h" #include "str.h" static int ipv6_parse_main(struct mystr* p_out_str, const struct mystr* p_in_str); static int ipv6_parse_hex(struct mystr* p_out_str, const struct mystr* p_in_str); static int ipv4_parse_dotquad(struct mystr* p_out_str, const struct mystr* p_in_str); const unsigned char* vsf_sysutil_parse_ipv6(const struct mystr* p_str) { static struct mystr s_ret; static struct mystr s_rhs_ret; static struct mystr s_lhs_str; static struct mystr s_rhs_str; unsigned int lhs_len; unsigned int rhs_len; str_empty(&s_ret); str_empty(&s_rhs_ret); str_copy(&s_lhs_str, p_str); str_split_text(&s_lhs_str, &s_rhs_str, "::"); if (!ipv6_parse_main(&s_ret, &s_lhs_str)) { return 0; } if (!ipv6_parse_main(&s_rhs_ret, &s_rhs_str)) { return 0; } lhs_len = str_getlen(&s_ret); rhs_len = str_getlen(&s_rhs_ret); if (lhs_len + rhs_len > 16) { return 0; } if (rhs_len > 0) { unsigned int add_nulls = 16 - (lhs_len + rhs_len); while (add_nulls--) { str_append_char(&s_ret, '\0'); } str_append_str(&s_ret, &s_rhs_ret); } return (const unsigned char*) str_getbuf(&s_ret); } const unsigned char* vsf_sysutil_parse_ipv4(const struct mystr* p_str) { static unsigned char items[4]; return vsf_sysutil_parse_uchar_string_sep(p_str, '.', items, sizeof(items)); } const unsigned char* vsf_sysutil_parse_uchar_string_sep( const struct mystr* p_str, char sep, unsigned char* p_items, unsigned int items) { static struct mystr s_tmp_str; unsigned int i; str_copy(&s_tmp_str, p_str); for (i=0; i 255) { return 0; } /* If this truncates from int to uchar, we don't care */ p_items[i] = (unsigned char) this_number; /* The right hand side of the comma now becomes the new string to * breakdown */ str_copy(&s_tmp_str, &s_rhs_sep_str); } return p_items; } static int ipv6_parse_main(struct mystr* p_out_str, const struct mystr* p_in_str) { static struct mystr s_lhs_str; static struct mystr s_rhs_str; struct str_locate_result loc_ret; str_copy(&s_lhs_str, p_in_str); while (!str_isempty(&s_lhs_str)) { str_split_char(&s_lhs_str, &s_rhs_str, ':'); if (str_isempty(&s_lhs_str)) { return 0; } loc_ret = str_locate_char(&s_lhs_str, '.'); if (loc_ret.found) { if (!ipv4_parse_dotquad(p_out_str, &s_lhs_str)) { return 0; } } else if (!ipv6_parse_hex(p_out_str, &s_lhs_str)) { return 0; } str_copy(&s_lhs_str, &s_rhs_str); } return 1; } static int ipv6_parse_hex(struct mystr* p_out_str, const struct mystr* p_in_str) { unsigned int len = str_getlen(p_in_str); unsigned int i; unsigned int val = 0; for (i=0; i= '0' && ch <= '9') { ch -= '0'; } else if (ch >= 'A' && ch <= 'F') { ch -= 'A'; ch += 10; } else { return 0; } val <<= 4; val |= ch; if (val > 0xFFFF) { return 0; } } str_append_char(p_out_str, (val >> 8)); str_append_char(p_out_str, (val & 0xFF)); return 1; } static int ipv4_parse_dotquad(struct mystr* p_out_str, const struct mystr* p_in_str) { unsigned int len = str_getlen(p_in_str); unsigned int i; unsigned int val = 0; unsigned int final_val = 0; int seen_char = 0; int dots = 0; for (i=0; i= '0' && ch <= '9') { ch -= '0'; val *= 10; val += ch; if (val > 255) { return 0; } seen_char = 1; } else { return 0; } } if (dots != 3 || !seen_char) { return 0; } final_val <<= 8; final_val |= val; str_append_char(p_out_str, (final_val >> 24)); str_append_char(p_out_str, ((final_val >> 16) & 0xFF)); str_append_char(p_out_str, ((final_val >> 8) & 0xFF)); str_append_char(p_out_str, (final_val & 0xFF)); return 1; } vsftpd-3.0.3/vsftpd.80000644000175000017500000000402111230162567013424 0ustar chrischris.\" Copyright (c) 2001 Daniel Jacobowitz .Dd March 8, 2001 .Dt VSFTPD 8 .Sh NAME .Nm vsftpd .Nd Very Secure FTP Daemon .Sh SYNOPSIS .Nm vsftpd .Op Ar configuration file and / or options .Sh DESCRIPTION .Nm vsftpd is the Very Secure File Transfer Protocol Daemon. The server can be launched via a .Dq super-server such as .Xr inetd 8 or .Xr xinetd 8 . Alternatively, vsftpd can be launched in standalone mode, in which case vsftpd itself will listen on the network. This latter mode is easier to use, and recommended. It is activated by setting .Pa listen=YES in .Pa /etc/vsftpd.conf . Direct execution of the .Nm vsftpd binary will then launch the FTP service ready for immediate client connections. .Sh OPTIONS An optional configuration file or files may be given on the command line. These files must be owned as root if running as root. Any command line option not starting with a "-" character is treated as a config file that will be loaded. Note that config files are loaded in the strict order that they are encountered on the command line. If no config files are specified, the default configuration file of .Pa /etc/vsftpd.conf will be loaded, after all other command line options are processed. .Pp Supported options are: .Bl -tag -width Ds .It Fl v Print the version information and exit, even if other options are encountered. .It Fl ooption=value Set a single option, value pair as per the format in the config file. Multiple -o options are supported, and they are applied in strict order relative to their appearance on the command line, including intermingling with loading of config files. .El .Sh EXAMPLES vsftpd -olisten=NO /etc/vsftpd.conf -oftpd_banner=blah .Pp That example overrides vsftpd's built-in default for the "listen" option to be NO, but then loads /etc/vsftpd.conf which may override that setting. Finally, the "ftpd_banner" setting is set to "blah", which overrides any default vsftpd setting and any identical setting that was in the config file. .Sh FILES .Pa /etc/vsftpd.conf .Sh SEE ALSO .Xr vsftpd.conf 5 .end vsftpd-3.0.3/REWARD0000644000175000017500000000017510750743677012757 0ustar chrischrisAt some stage, a reward may be offered to anyone finding a serious security hole in vsftpd. Mail me if you have any ideas :) vsftpd-3.0.3/sslslave.c0000644000175000017500000000700011736505503014027 0ustar chrischris/* * Part of Very Secure FTPd * Licence: GPL v2 * Author: Chris Evans * sslslave.c */ #include "sslslave.h" #include "session.h" #include "privsock.h" #include "tunables.h" #include "sysutil.h" #include "sysdeputil.h" #include "utility.h" #include "ssl.h" #include "readwrite.h" #include "defs.h" void ssl_slave(struct vsf_session* p_sess) { struct mystr data_str = INIT_MYSTR; str_reserve(&data_str, VSFTP_DATA_BUFSIZE); /* Before becoming the slave, clear the alarm for the FTP protocol. */ vsf_sysutil_clear_alarm(); /* No need for any further communications with the privileged parent. */ priv_sock_set_parent_context(p_sess); if (tunable_setproctitle_enable) { vsf_sysutil_setproctitle("SSL handler"); } while (1) { char cmd = priv_sock_get_cmd(p_sess->ssl_slave_fd); int ret; if (cmd == PRIV_SOCK_GET_USER_CMD) { ret = ftp_getline(p_sess, &p_sess->ftp_cmd_str, p_sess->p_control_line_buf); priv_sock_send_int(p_sess->ssl_slave_fd, ret); if (ret >= 0) { priv_sock_send_str(p_sess->ssl_slave_fd, &p_sess->ftp_cmd_str); } } else if (cmd == PRIV_SOCK_WRITE_USER_RESP) { priv_sock_get_str(p_sess->ssl_slave_fd, &p_sess->ftp_cmd_str); ret = ftp_write_str(p_sess, &p_sess->ftp_cmd_str, kVSFRWControl); priv_sock_send_int(p_sess->ssl_slave_fd, ret); } else if (cmd == PRIV_SOCK_DO_SSL_HANDSHAKE) { char result = PRIV_SOCK_RESULT_BAD; if (p_sess->data_fd != -1 || p_sess->p_data_ssl != 0) { bug("state not clean"); } p_sess->data_fd = priv_sock_recv_fd(p_sess->ssl_slave_fd); ret = ssl_accept(p_sess, p_sess->data_fd); if (ret == 1) { result = PRIV_SOCK_RESULT_OK; } else { vsf_sysutil_close(p_sess->data_fd); p_sess->data_fd = -1; } priv_sock_send_result(p_sess->ssl_slave_fd, result); } else if (cmd == PRIV_SOCK_DO_SSL_READ) { int size = priv_sock_get_int(p_sess->ssl_slave_fd); if (size <= 0 || size > VSFTP_DATA_BUFSIZE) { bug("bad size"); } if (p_sess->data_fd == -1 || p_sess->p_data_ssl == 0) { bug("invalid state"); } str_trunc(&data_str, (unsigned int) size); ret = ssl_read_into_str(p_sess, p_sess->p_data_ssl, &data_str); priv_sock_send_int(p_sess->ssl_slave_fd, ret); priv_sock_send_str(p_sess->ssl_slave_fd, &data_str); } else if (cmd == PRIV_SOCK_DO_SSL_WRITE) { if (p_sess->data_fd == -1 || p_sess->p_data_ssl == 0) { bug("invalid state"); } priv_sock_get_str(p_sess->ssl_slave_fd, &data_str); ret = ssl_write(p_sess->p_data_ssl, str_getbuf(&data_str), str_getlen(&data_str)); priv_sock_send_int(p_sess->ssl_slave_fd, ret); } else if (cmd == PRIV_SOCK_DO_SSL_CLOSE) { char result = PRIV_SOCK_RESULT_BAD; if (p_sess->data_fd == -1 && p_sess->p_data_ssl == 0) { result = PRIV_SOCK_RESULT_OK; } else { if (p_sess->data_fd == -1 || p_sess->p_data_ssl == 0) { bug("invalid state"); } ret = ssl_data_close(p_sess); if (ret == 1) { result = PRIV_SOCK_RESULT_OK; } vsf_sysutil_close(p_sess->data_fd); p_sess->data_fd = -1; } priv_sock_send_result(p_sess->ssl_slave_fd, result); } else { die("bad request in process_ssl_slave_req"); } } } vsftpd-3.0.3/prelogin.c0000644000175000017500000002040111736504403014010 0ustar chrischris/* * Part of Very Secure FTPd * Licence: GPL v2 * Author: Chris Evans * prelogin.c * * Code to parse the FTP protocol prior to a successful login. */ #include "prelogin.h" #include "ftpcmdio.h" #include "ftpcodes.h" #include "str.h" #include "vsftpver.h" #include "tunables.h" #include "oneprocess.h" #include "twoprocess.h" #include "sysdeputil.h" #include "sysutil.h" #include "session.h" #include "banner.h" #include "logging.h" #include "ssl.h" #include "features.h" #include "defs.h" #include "opts.h" /* Functions used */ static void check_limits(struct vsf_session* p_sess); static void emit_greeting(struct vsf_session* p_sess); static void parse_username_password(struct vsf_session* p_sess); static void handle_user_command(struct vsf_session* p_sess); static void handle_pass_command(struct vsf_session* p_sess); static void handle_get(struct vsf_session* p_sess); static void check_login_delay(); static void check_login_fails(struct vsf_session* p_sess); void init_connection(struct vsf_session* p_sess) { if (tunable_setproctitle_enable) { vsf_sysutil_setproctitle("not logged in"); } /* Before we talk to the remote, make sure an alarm is set up in case * writing the initial greetings should block. */ vsf_cmdio_set_alarm(p_sess); /* Check limits before doing an implicit SSL handshake, to avoid DoS * attacks. This will result in plain text messages being sent to the SSL * client, but we can live with that. */ check_limits(p_sess); if (tunable_ssl_enable && tunable_implicit_ssl) { ssl_control_handshake(p_sess); } if (tunable_ftp_enable) { emit_greeting(p_sess); } parse_username_password(p_sess); } static void check_limits(struct vsf_session* p_sess) { struct mystr str_log_line = INIT_MYSTR; /* Check for client limits (standalone mode only) */ if (tunable_max_clients > 0 && p_sess->num_clients > tunable_max_clients) { str_alloc_text(&str_log_line, "Connection refused: too many sessions."); vsf_log_line(p_sess, kVSFLogEntryConnection, &str_log_line); vsf_cmdio_write_exit(p_sess, FTP_TOO_MANY_USERS, "There are too many connected users, please try later.", 1); } if (tunable_max_per_ip > 0 && p_sess->num_this_ip > tunable_max_per_ip) { str_alloc_text(&str_log_line, "Connection refused: too many sessions for this address."); vsf_log_line(p_sess, kVSFLogEntryConnection, &str_log_line); vsf_cmdio_write_exit(p_sess, FTP_IP_LIMIT, "There are too many connections from your internet address.", 1); } if (!p_sess->tcp_wrapper_ok) { str_alloc_text(&str_log_line, "Connection refused: tcp_wrappers denial."); vsf_log_line(p_sess, kVSFLogEntryConnection, &str_log_line); vsf_cmdio_write_exit(p_sess, FTP_IP_DENY, "Service not available.", 1); } vsf_log_line(p_sess, kVSFLogEntryConnection, &str_log_line); } static void emit_greeting(struct vsf_session* p_sess) { if (!str_isempty(&p_sess->banner_str)) { vsf_banner_write(p_sess, &p_sess->banner_str, FTP_GREET); str_free(&p_sess->banner_str); vsf_cmdio_write(p_sess, FTP_GREET, ""); } else if (tunable_ftpd_banner == 0) { vsf_cmdio_write(p_sess, FTP_GREET, "(vsFTPd " VSF_VERSION ")"); } else { vsf_cmdio_write(p_sess, FTP_GREET, tunable_ftpd_banner); } } static void parse_username_password(struct vsf_session* p_sess) { while (1) { vsf_cmdio_get_cmd_and_arg(p_sess, &p_sess->ftp_cmd_str, &p_sess->ftp_arg_str, 1); if (tunable_ftp_enable) { if (str_equal_text(&p_sess->ftp_cmd_str, "USER")) { handle_user_command(p_sess); } else if (str_equal_text(&p_sess->ftp_cmd_str, "PASS")) { handle_pass_command(p_sess); } else if (str_equal_text(&p_sess->ftp_cmd_str, "QUIT")) { vsf_cmdio_write_exit(p_sess, FTP_GOODBYE, "Goodbye.", 0); } else if (str_equal_text(&p_sess->ftp_cmd_str, "FEAT")) { handle_feat(p_sess); } else if (str_equal_text(&p_sess->ftp_cmd_str, "OPTS")) { handle_opts(p_sess); } else if (tunable_ssl_enable && str_equal_text(&p_sess->ftp_cmd_str, "AUTH") && !p_sess->control_use_ssl) { handle_auth(p_sess); } else if (tunable_ssl_enable && str_equal_text(&p_sess->ftp_cmd_str, "PBSZ")) { handle_pbsz(p_sess); } else if (tunable_ssl_enable && str_equal_text(&p_sess->ftp_cmd_str, "PROT")) { handle_prot(p_sess); } else if (str_isempty(&p_sess->ftp_cmd_str) && str_isempty(&p_sess->ftp_arg_str)) { /* Deliberately ignore to avoid NAT device bugs, as per ProFTPd. */ } else { vsf_cmdio_write(p_sess, FTP_LOGINERR, "Please login with USER and PASS."); } } else if (tunable_http_enable) { if (str_equal_text(&p_sess->ftp_cmd_str, "GET")) { handle_get(p_sess); } else { vsf_cmdio_write(p_sess, FTP_LOGINERR, "Bad HTTP verb."); } vsf_sysutil_exit(0); } } } static void handle_get(struct vsf_session* p_sess) { p_sess->is_http = 1; str_copy(&p_sess->http_get_arg, &p_sess->ftp_arg_str); str_alloc_text(&p_sess->user_str, "FTP"); str_alloc_text(&p_sess->ftp_arg_str, ""); handle_pass_command(p_sess); } static void handle_user_command(struct vsf_session* p_sess) { /* SECURITY: If we're in anonymous only-mode, immediately reject * non-anonymous usernames in the hope we save passwords going plaintext * over the network */ int is_anon = 1; str_copy(&p_sess->user_str, &p_sess->ftp_arg_str); str_upper(&p_sess->ftp_arg_str); if (!str_equal_text(&p_sess->ftp_arg_str, "FTP") && !str_equal_text(&p_sess->ftp_arg_str, "ANONYMOUS")) { is_anon = 0; } if (!tunable_local_enable && !is_anon) { vsf_cmdio_write( p_sess, FTP_LOGINERR, "This FTP server is anonymous only."); str_empty(&p_sess->user_str); return; } if (is_anon && p_sess->control_use_ssl && !tunable_allow_anon_ssl && !tunable_force_anon_logins_ssl) { vsf_cmdio_write( p_sess, FTP_LOGINERR, "Anonymous sessions may not use encryption."); str_empty(&p_sess->user_str); return; } if (tunable_ssl_enable && !is_anon && !p_sess->control_use_ssl && tunable_force_local_logins_ssl) { vsf_cmdio_write_exit( p_sess, FTP_LOGINERR, "Non-anonymous sessions must use encryption.", 1); } if (tunable_ssl_enable && is_anon && !p_sess->control_use_ssl && tunable_force_anon_logins_ssl) { vsf_cmdio_write_exit( p_sess, FTP_LOGINERR, "Anonymous sessions must use encryption.", 1); } if (tunable_userlist_enable) { int located = str_contains_line(&p_sess->userlist_str, &p_sess->user_str); if ((located && tunable_userlist_deny) || (!located && !tunable_userlist_deny)) { check_login_delay(); vsf_cmdio_write(p_sess, FTP_LOGINERR, "Permission denied."); check_login_fails(p_sess); str_empty(&p_sess->user_str); return; } } if (is_anon && tunable_no_anon_password) { /* Fake a password */ str_alloc_text(&p_sess->ftp_arg_str, ""); handle_pass_command(p_sess); } else { vsf_cmdio_write(p_sess, FTP_GIVEPWORD, "Please specify the password."); } } static void handle_pass_command(struct vsf_session* p_sess) { if (str_isempty(&p_sess->user_str)) { vsf_cmdio_write(p_sess, FTP_NEEDUSER, "Login with USER first."); return; } /* These login calls never return if successful */ if (tunable_one_process_model) { vsf_one_process_login(p_sess, &p_sess->ftp_arg_str); } else { vsf_two_process_login(p_sess, &p_sess->ftp_arg_str); } vsf_cmdio_write(p_sess, FTP_LOGINERR, "Login incorrect."); check_login_fails(p_sess); str_empty(&p_sess->user_str); /* FALLTHRU if login fails */ } static void check_login_delay() { if (tunable_delay_failed_login) { vsf_sysutil_sleep((double) tunable_delay_failed_login); } } static void check_login_fails(struct vsf_session* p_sess) { if (++p_sess->login_fails >= tunable_max_login_fails) { vsf_sysutil_shutdown_failok(VSFTP_COMMAND_FD); vsf_sysutil_exit(1); } } vsftpd-3.0.3/logging.c0000644000175000017500000002433212025250601013614 0ustar chrischris/* * Part of Very Secure FTPd * Licence: GPL v2 * Author: Chris Evans * * logging.c */ #include "logging.h" #include "tunables.h" #include "utility.h" #include "str.h" #include "sysutil.h" #include "sysstr.h" #include "session.h" /* File local functions */ static int vsf_log_type_is_transfer(enum EVSFLogEntryType type); static void vsf_log_common(struct vsf_session* p_sess, int succeeded, enum EVSFLogEntryType what, const struct mystr* p_str); static void vsf_log_do_log_vsftpd_format(struct vsf_session* p_sess, struct mystr* p_str, int succeeded, enum EVSFLogEntryType what, const struct mystr* p_log_str); static void vsf_log_do_log_wuftpd_format(struct vsf_session* p_sess, struct mystr* p_str, int succeeded); static void vsf_log_do_log_to_file(int fd, struct mystr* p_str); void vsf_log_init(struct vsf_session* p_sess) { if (tunable_syslog_enable || tunable_tcp_wrappers) { vsf_sysutil_openlog(1); } if (!tunable_xferlog_enable && !tunable_dual_log_enable) { return; } if (tunable_dual_log_enable || tunable_xferlog_std_format) { int retval = -1; if (tunable_xferlog_file) { retval = vsf_sysutil_create_or_open_file_append(tunable_xferlog_file, 0600); } if (vsf_sysutil_retval_is_error(retval)) { die2("failed to open xferlog log file:", tunable_xferlog_file); } p_sess->xferlog_fd = retval; } if (tunable_dual_log_enable || !tunable_xferlog_std_format) { if (!tunable_syslog_enable) { int retval = -1; if (tunable_vsftpd_log_file) { retval = vsf_sysutil_create_or_open_file_append(tunable_vsftpd_log_file, 0600); } if (vsf_sysutil_retval_is_error(retval)) { die2("failed to open vsftpd log file:", tunable_vsftpd_log_file); } p_sess->vsftpd_log_fd = retval; } } } static int vsf_log_type_is_transfer(enum EVSFLogEntryType type) { return (type == kVSFLogEntryDownload || type == kVSFLogEntryUpload); } void vsf_log_start_entry(struct vsf_session* p_sess, enum EVSFLogEntryType what) { if (p_sess->log_type != 0) { bug("non null log_type in vsf_log_start_entry"); } p_sess->log_type = (unsigned long) what; p_sess->log_start_sec = 0; p_sess->log_start_usec = 0; p_sess->transfer_size = 0; str_empty(&p_sess->log_str); if (vsf_log_type_is_transfer(what)) { p_sess->log_start_sec = vsf_sysutil_get_time_sec(); p_sess->log_start_usec = vsf_sysutil_get_time_usec(); } } void vsf_log_line(struct vsf_session* p_sess, enum EVSFLogEntryType what, struct mystr* p_str) { vsf_log_common(p_sess, 1, what, p_str); } int vsf_log_entry_pending(struct vsf_session* p_sess) { if (p_sess->log_type == 0) { return 0; } return 1; } void vsf_log_clear_entry(struct vsf_session* p_sess) { p_sess->log_type = 0; } void vsf_log_do_log(struct vsf_session* p_sess, int succeeded) { vsf_log_common(p_sess, succeeded, (enum EVSFLogEntryType) p_sess->log_type, &p_sess->log_str); p_sess->log_type = 0; } static void vsf_log_common(struct vsf_session* p_sess, int succeeded, enum EVSFLogEntryType what, const struct mystr* p_str) { static struct mystr s_log_str; /* Handle xferlog line if appropriate */ if (p_sess->xferlog_fd != -1 && vsf_log_type_is_transfer(what)) { vsf_log_do_log_wuftpd_format(p_sess, &s_log_str, succeeded); vsf_log_do_log_to_file(p_sess->xferlog_fd, &s_log_str); } /* Handle vsftpd.log line if appropriate */ if (p_sess->vsftpd_log_fd != -1) { vsf_log_do_log_vsftpd_format(p_sess, &s_log_str, succeeded, what, p_str); vsf_log_do_log_to_file(p_sess->vsftpd_log_fd, &s_log_str); } /* Handle syslog() line if appropriate */ if (tunable_syslog_enable) { int severe = 0; vsf_log_do_log_vsftpd_format(p_sess, &s_log_str, succeeded, what, p_str); if (what == kVSFLogEntryLogin && !succeeded) { severe = 1; } str_syslog(&s_log_str, severe); } } static void vsf_log_do_log_to_file(int fd, struct mystr* p_str) { if (!tunable_no_log_lock) { int retval = vsf_sysutil_lock_file_write(fd); if (vsf_sysutil_retval_is_error(retval)) { return; } } str_replace_unprintable(p_str, '?'); str_append_char(p_str, '\n'); /* Ignore write failure; maybe the disk filled etc. */ (void) str_write_loop(p_str, fd); if (!tunable_no_log_lock) { vsf_sysutil_unlock_file(fd); } } static void vsf_log_do_log_wuftpd_format(struct vsf_session* p_sess, struct mystr* p_str, int succeeded) { static struct mystr s_filename_str; long delta_sec; enum EVSFLogEntryType what = (enum EVSFLogEntryType) p_sess->log_type; /* Date - vsf_sysutil_get_current_date updates cached time */ str_alloc_text(p_str, vsf_sysutil_get_current_date()); str_append_char(p_str, ' '); /* Transfer time (in seconds) */ delta_sec = vsf_sysutil_get_time_sec() - p_sess->log_start_sec; if (delta_sec <= 0) { delta_sec = 1; } str_append_ulong(p_str, (unsigned long) delta_sec); str_append_char(p_str, ' '); /* Remote host name */ str_append_str(p_str, &p_sess->remote_ip_str); str_append_char(p_str, ' '); /* Bytes transferred */ str_append_filesize_t(p_str, p_sess->transfer_size); str_append_char(p_str, ' '); /* Filename */ str_copy(&s_filename_str, &p_sess->log_str); str_replace_char(&s_filename_str, ' ', '_'); str_append_str(p_str, &s_filename_str); str_append_char(p_str, ' '); /* Transfer type (ascii/binary) */ if (p_sess->is_ascii) { str_append_text(p_str, "a "); } else { str_append_text(p_str, "b "); } /* Special action flag - tar, gzip etc. */ str_append_text(p_str, "_ "); /* Direction of transfer */ if (what == kVSFLogEntryUpload) { str_append_text(p_str, "i "); } else { str_append_text(p_str, "o "); } /* Access mode: anonymous/real user, and identity */ if (p_sess->is_anonymous && !p_sess->is_guest) { str_append_text(p_str, "a "); str_append_str(p_str, &p_sess->anon_pass_str); } else { if (p_sess->is_guest) { str_append_text(p_str, "g "); } else { str_append_text(p_str, "r "); } str_append_str(p_str, &p_sess->user_str); } str_append_char(p_str, ' '); /* Service name, authentication method, authentication user id */ str_append_text(p_str, "ftp 0 * "); /* Completion status */ if (succeeded) { str_append_char(p_str, 'c'); } else { str_append_char(p_str, 'i'); } } static void vsf_log_do_log_vsftpd_format(struct vsf_session* p_sess, struct mystr* p_str, int succeeded, enum EVSFLogEntryType what, const struct mystr* p_log_str) { str_empty(p_str); if (!tunable_syslog_enable) { /* Date - vsf_sysutil_get_current_date updates cached time */ str_append_text(p_str, vsf_sysutil_get_current_date()); /* Pid */ str_append_text(p_str, " [pid "); str_append_ulong(p_str, vsf_sysutil_getpid()); str_append_text(p_str, "] "); } /* User */ if (!str_isempty(&p_sess->user_str)) { str_append_char(p_str, '['); str_append_str(p_str, &p_sess->user_str); str_append_text(p_str, "] "); } /* And the action */ if (what != kVSFLogEntryFTPInput && what != kVSFLogEntryFTPOutput && what != kVSFLogEntryConnection && what != kVSFLogEntryDebug) { if (succeeded) { str_append_text(p_str, "OK "); } else { str_append_text(p_str, "FAIL "); } } switch (what) { case kVSFLogEntryDownload: str_append_text(p_str, "DOWNLOAD"); break; case kVSFLogEntryUpload: str_append_text(p_str, "UPLOAD"); break; case kVSFLogEntryMkdir: str_append_text(p_str, "MKDIR"); break; case kVSFLogEntryLogin: str_append_text(p_str, "LOGIN"); break; case kVSFLogEntryFTPInput: str_append_text(p_str, "FTP command"); break; case kVSFLogEntryFTPOutput: str_append_text(p_str, "FTP response"); break; case kVSFLogEntryConnection: str_append_text(p_str, "CONNECT"); break; case kVSFLogEntryDelete: str_append_text(p_str, "DELETE"); break; case kVSFLogEntryRename: str_append_text(p_str, "RENAME"); break; case kVSFLogEntryRmdir: str_append_text(p_str, "RMDIR"); break; case kVSFLogEntryChmod: str_append_text(p_str, "CHMOD"); break; case kVSFLogEntryDebug: str_append_text(p_str, "DEBUG"); break; case kVSFLogEntryNull: /* Fall through */ default: bug("bad entry_type in vsf_log_do_log"); break; } str_append_text(p_str, ": Client \""); str_append_str(p_str, &p_sess->remote_ip_str); str_append_char(p_str, '"'); if (what == kVSFLogEntryLogin && !str_isempty(&p_sess->anon_pass_str)) { str_append_text(p_str, ", anon password \""); str_append_str(p_str, &p_sess->anon_pass_str); str_append_char(p_str, '"'); } if (!str_isempty(p_log_str)) { str_append_text(p_str, ", \""); str_append_str(p_str, p_log_str); str_append_char(p_str, '"'); } if (what != kVSFLogEntryFTPInput && what != kVSFLogEntryFTPOutput && what != kVSFLogEntryDebug) { if (p_sess->transfer_size) { str_append_text(p_str, ", "); str_append_filesize_t(p_str, p_sess->transfer_size); str_append_text(p_str, " bytes"); } if (vsf_log_type_is_transfer(what)) { long delta_sec = vsf_sysutil_get_time_sec() - p_sess->log_start_sec; long delta_usec = vsf_sysutil_get_time_usec() - p_sess->log_start_usec; double time_delta = (double) delta_sec + ((double) delta_usec / (double) 1000000); double kbyte_rate; if (time_delta <= 0) { time_delta = 0.1; } kbyte_rate = ((double) p_sess->transfer_size / time_delta) / (double) 1024; str_append_text(p_str, ", "); str_append_double(p_str, kbyte_rate); str_append_text(p_str, "Kbyte/sec"); } } } vsftpd-3.0.3/TODO0000644000175000017500000000345111737172424012530 0ustar chrischrisCRITICAL ======== NOT SO CRITICAL =============== - ABOR handling broken for SSL connections (does any client actually use it?) - Add -Wconversion - Don't leak SSL private key to compromised process? May be impossible. We'll see, would be an interesting security story. - Better reporting of failed uploads due to out of device space or quota all used. - Upload file size limits. - Improve FAQ, docs (ongoing..) - Sweedish, Russian etc. characters showing as ? in the log - many complaints. - "add_group" support. - Still reports FlashFXP broken when trying to do FXP. - Add negation, other support to regex handler. - Allow groups to be listed in user lists. - Allow space in username. - Minor: background should happen after listen has completed so that failure can result in a non-zero exit code. - option to chroot to home dir and THEN apply init_dir - separate upload/download max rates - select() is assuming Linux behaviour (not threatening stability) - add example global bandwidth limiting. - have a chown_uploads for non-anon users too; also more control over permissions of uploaded file ON THE BACK BURNER ================== - MLST, MLSD - LPRT, LPSV - log logout (pam session support provides this for locals) - Limits on GIDs allowed to authenticate? - Dynamic login info e.g. you are user XXX of YYY. - Handle SIGINT. - Session byte transfer counts in STAT output. - Test vsftpd with pam_opie (issues with challenge/response vs. FTP protocol?) - SITE GROUP support. - SITE UTIME? - Allow listener to listen on multiple IPs, protocols; bonus points if the different IPs can have different configs. NOT PLANNED =========== - telnet strings (no demand) - "Minimal" build support - transparent tar / compression support (no demand) - put anon FTP users in wtmp too? - Integrated test suite (I'm so lazy..) SORRY. vsftpd-3.0.3/FAQ0000644000175000017500000003121611673167336012377 0ustar chrischrisvsftpd frequently asked questions!! ----------------------------------- Q) Can I restrict users to their home directories? A) Yes. You are probably after the setting: chroot_local_user=YES Q) Why don't symlinks work with chroot_local_user=YES? A) This is a consequence of how chroot() security works. As alternatives, look into hard links, or if you have a modern Linux, see the powerful "mount --bind". Q) Does vsftpd support a limit on the number of users connected? A1) Yes, indirectly. vsftpd is an inetd-based service. If use the popular "xinetd" as your inetd, this supports per-service per-IP connection limits. There is an example of this in the "EXAMPLE" directory. A2) If you run vsftpd in "standalone" mode with the setting listen=YES, then you can investigate the setting (e.g.): max_clients=10 Q) Help! I'm getting the error message "refusing to run with writable root". A) vsftpd is protecting against dangerous configurations. The cause of this message is usually dodgy ownership of the ftp home directory. The home directory should NOT be owned by the ftp user itself. Neither should it be writable by the ftp user. A way to fix this is: chown root ~ftp; chmod -w ~ftp Another cause might be an attempt to use chroot_local_user without setting up the directory ownership properly. Q) Help! I'm getting the error message "str_getpwnam". A) The most likely cause of this is that the user that is configured as the 'nopriv_user' setting (often 'nobody') does not exist on your system. vsftpd needs this user to run bits of itself with no privilege. Q) Help! Local users cannot log in. A) There are various possible problems. A1) By default, vsftpd disables any logins other than anonymous logins. Put local_enable=YES in your /etc/vsftpd.conf to allow local users to log in. A2) vsftpd tries to link with PAM. (Run "ldd vsftpd" and look for libpam to find out whether this has happened or not). If vsftpd links with PAM, then you will need to have a PAM file installed for the vsftpd service. There is a sample one for RedHat systems included in the "RedHat" directory - put it under /etc/pam.d A3) If vsftpd didn't link with PAM, then there are various possible issues. Is the user's shell in /etc/shells? If you have shadowed passwords, does your system have a "shadow.h" file in the include path? A4) If you are not using PAM, then vsftpd will do its own check for a valid user shell in /etc/shells. You may need to disable this if you use an invalid shell to disable logins other than FTP logins. Put check_shell=NO in your /etc/vsftpd.conf. Q) Help! Uploads or other write commands give me "500 Unknown command.". A) By default, write commands, including uploads and new directories, are disabled. This is a security measure. To enable writes, put write_enable=YES in your /etc/vsftpd.conf. Q) Help! What are the security implications referred to in the "chroot_local_user" option? A) Firstly note that other ftp daemons have the same implications. It is a generic problem. The problem isn't too severe, but it is this: Some people have FTP user accounts which are not trusted to have full shell access. If these accounts can also upload files, there is a small risk. A bad user now has control of the filesystem root, which is their home directory. The ftp daemon might cause some config file to be read - e.g. /etc/some_file. With chroot(), this file is now under the control of the user. vsftpd is careful in this area. But, the system's libc might want to open locale config files or other settings... Q) Help! Uploaded files are appearing with permissions -rw-------. A1) Depending on if this is an upload by a local user or an anonymous user, use "local_umask" or "anon_umask" to change this. For example, use "anon_umask=022" to give anonymously uploaded files permissions -rw-r--r--. Note that the "0" before the "22" is important. A2) Also see the vsftpd.conf.5 man page for the new "file_open_mode" parameter. Q) Help! How do I integrate with LDAP users and logins? A) Use vsftpd's PAM integration to do this, and have PAM authenticate against an LDAP repository. Q) Help! Does vsftpd do virtual hosting setups? A1) Yes. If you integrate vsftpd with xinetd, you can use xinetd to bind to several different IP addresses. For each IP address, get xinetd to launch vsftpd with a different config file. This way, you can get different behaviour per virtual address. A2) Alternatively, run as many copies as vsftpd as necessary, in standalone mode. Use "listen_address=x.x.x.x" to set the virtual IP. Q) Help! Does vsftpd support virtual users? A) Yes, via PAM integration. Set "guest_enable=YES" in /etc/vsftpd.conf. This has the effect of mapping every non-anonymous successful login to the local username specified in "guest_username". Then, use PAM and (e.g.) its pam_userdb module to provide authentication against an external (i.e. non-/etc/passwd) repository of users. Note - currently there is a restriction that with guest_enable enabled, local users also get mapped to guest_username. There is an example of virtual users setup in the "EXAMPLE" directory. Q) Help! Does vsftpd support different settings for different users? A) Yes - in a very powerful way. Look at the setting "user_config_dir" in the manual page. Q) Help! Can I restrict vsftpd data connections to a specific range of ports? A) Yes. See the config settings "pasv_min_port" and "pasv_max_port". Q) Help! I'm getting the message "OOPS: chdir". A) If this is for an anonymous login, check that the home directory for the user "ftp" is correct. If you are using the config setting "anon_root", check that is correct too. Q) Help! vsftpd is reporting times as GMT times and not local times! A) This behaviour can be changed with the setting "use_localtime=YES". Q) Help! Can I disable certain FTP commands? A) Yes. There are some individual settings (e.g. dirlist_enable) or you can specify a complete set of allowed commands with "cmds_allowed". Q) Help! Can I change the port that vsftpd runs on? A1) Yes. If you are running vsftpd in standalone mode, use the "listen_port" directive in vsftpd.conf. A2) Yes. If you are running vsftpd from an inetd or xinetd program, this becomes an inetd or xinetd problem. You must change the inetd or xinetd configuration files (perhaps /etc/inetd.conf or /etc/xinetd.d/vsftpd) Q) Help! Will vsftpd authenticate against an LDAP server? What about a MySQL server? A) Yes. vsftpd uses PAM for authentication, so you need to configure PAM to use pam_ldap or pam_mysql modules. This may involve installing the PAM modules and then editing the PAM config file (perhaps /etc/pam.d/vsftpd). Q) Help! Does vsftpd support per-IP limits? A1) Yes. If you are running vsftpd standalone, there is a "max_per_ip" setting. A2) Yes. If you are running vsftpd via xinetd, there is an xinetd config variable "per_source". Q) Help! Does vsftpd support bandwidth limiting? A) Yes. See vsftpd.conf.5 man page and investigate settings such as "anon_max_rate" and "local_max_rate". Q) Help! Does vsftpd support IP-based access control? A1) Yes. vsftpd can integrate with tcp_wrappers (if built with this support). It is enabled with the setting "tcp_wrappers=YES". A2) Yes. vsftpd can be run from xinetd, which supports tcp_wrappers integration. Q) Help! Does vsftpd support IPv6? A) Yes, as of version 1.2.0. Read the vsftpd.conf.5 man page. Q) Help! vsftpd doesn't build, it fails with an error about being unable to find -lcap. A) Install the libcap package and retry the build. Seems to affect Debian users a lot. A) Install the libcap-devel. This certainly affects Fedora. Q) Help! I've put settings in /etc/vsftpd.conf, but they are not taking effect! A) This is affecting some RedHat users - some RedHat versions put the config file in /etc/vsftpd/vsftpd.conf. Q) Help! vsftpd doesn't build, it complains about problems with incomplete types in sysutil.c. A) Your system probably doesn't have IPv6 support. Either use a more modern system, use an older vsftpd (e.g. v1.1.3), or wait for a version of vsftpd without this problem! Q) Help! I'm getting messages along the lines of 500 OOPS: vsf_sysutil_bind when trying to do downloads (particularly lots of small files). A) vsftpd-1.2.1 should sort this out. Q) Help! Does vsftpd support hiding or denying certain files? A) Yes. Look at the hide_file and deny_file options in the manual page. Q) Help! Does vsftpd support FXP? A) Yes. An FTP server does not have to do anything special to support FXP. However, you many get tripped up by vsftpd's security precautions on IP addresses. In order to relax these precautions, have a look in the vsftpd.conf.5 for pasv_promiscuous (and the less advisable port_promiscuous). Q) Help! I'm getting the error "426 Failure writing network stream." on downloads. A) You shouldn't see this with v1.2.1 or newer versions of vsftpd. Older versions of vsftpd can give this error if the user tries to download something from an unusual filesystem (e.g. FAT), which don't support performance features used by vsftpd. With vsftpd-1.1.3 and newer there is a config workaround, use_sendfile=NO. Q) Help! I'm using the pam_userdb login module and the login hangs. A) This could be a bad interaction with glibc version 2.3 and PAM. A Debian user reported this. The initial report is here: http://lists.debian.org/debian-glibc/2003/debian-glibc-200309/msg00310.html Q) Help! Does vsftpd support large files (>2Gb?). A) Yes, it does. Q) Help! Well, large file support doesn't seem to be working, then! A1) Large file support first appeared in v1.1.0. A2) Solaris large file support wasn't fixed until v1.2.2. A3) FreeBSD large file support wasn't fixed until v1.2.2. A4) The early Linux 2.6 kernels had a bug in this area - use v2.6.6 or newer. A5) Are you sure your FTP _client_ correctly supports large files? Q) Help! The built-in vsftpd listener is hanging or crashing! A) A bug in this area is fixed in vsftpd v1.2.2. The problem has always existed but seems to frequently trigger only on certain platforms. For example, Fedora Core 1 - the suspected trigger is a glibc-2.3 platform, possibly in combination with a NPTL-enabled kernel. Q) Help! I'm using Solaris / Veritas and vsftpd is hanging! A) Suspected bug with the Solaris / Veritas combination. With vsftpd-1.2.3 there is a possible workaround: no_log_lock=YES in your vsftpd.conf.5. Q) Does vsftpd support SSL / TLS based encryption? A) Yes, as of v2.0.0, this is supported for the control and data connections (hurrah). You need a build of vsftpd with this support enabled, and then you need to activate the ssl_enable setting. NOTE there are security considerations with this support. Please make sure to read the ssl_enable section in the vsftpd.conf.5 man page thoroughly before using. Q) Help! I'm using FlashFXP and getting truncated files on download. A) FlashFXP is buggy - particularly with SSL transfers. Upgrade to v3.0RC4 or newer, which is reported to be fixed. Q) Help! I'm trying to build vsftpd, and I get an error along the lines of "krb5.h: no such file or directory". A) Yes, seems to be a problem with some RedHat setups. See http://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=111301 for details and suggested workarounds. Q) Help! I'm getting the error "OOPS: capset" when I try to connect to vsftpd. A) This is an issue with SELinux enabled distributions. The solution is to make sure the capability kernel module is loaded. Q) Help! I'm getting the error "ftp: netin: Interrupted system call". A) Seems to be a bug in ftp-tls, particularly with SSL transfers with bandwidth limiting in effect. Q) Help! When trying SSL transfers, users log in and are no longer restricted to their home directory! They can browse the entire filesystem! A) Most likely, your FTP client is in fact using the SSH protocol rather than the FTP protocol - so sshd is in control and not vsftpd! A) Of course, make sure you turn on the chroot_local_user option!! Q) Help! I'm getting connections dropped whilst using gFTP for an SSL connection. A) The version of gFTP on my Fedora Core 10 installation appears to send the "SIZE" command plain text during an SSL connection, which obviously breaks the SSL connection. Q) Help! SSL data connections are not working. A) As of v2.1.0, vsftpd only accepts data connections that are reused sessions of the control connection. This is a security measure. Unfortunately, not all FTP clients reuse sessions (e.g. curl). You can disable this requirement by changing require_ssl_reuse to NO. Q) Help! My LDAP / mysql / etc. authentication and / or username lookup are failing! A) As of v2.2.0, the built-in sandboxing uses network isolation on Linux. This may be interfering with any module that needs to use the network to perform operations or lookups. Try changing isolate_network to NO. Q) Blah.. blah.. A) For a good idea of what vsftpd can do, read the vsftpd.conf.5 man page and the EXAMPLES. vsftpd-3.0.3/ptracesandbox.c0000644000175000017500000010004511672070016015026 0ustar chrischris/* * Part of Very Secure FTPd * Licence: GPL v2 * Author: Chris Evans * ptracesandbox.c * * Generic routines to setup and run a process under a restrictive ptrace() * based sandbox. * Note that the style in this file is to not go via the helper functions in * sysutil.c, but instead hit the system APIs directly. This is because I may * very well release just this file to the public domain, and do not want * dependencies on other parts of vsftpd. */ #include "ptracesandbox.h" #if defined(__linux__) && defined(__i386__) #include #include #include /* For AF_MAX (NPROTO is defined to this) */ #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef __NR_sendfile64 #define __NR_sendfile64 239 #endif #ifndef __NR_exit_group #define __NR_exit_group 252 #endif #ifndef __NR_utimes #define __NR_utimes 271 #endif /* For the socketcall() multiplex args. */ #include #ifndef PTRACE_SETOPTIONS #define PTRACE_SETOPTIONS 0x4200 #endif #ifndef PTRACE_O_TRACESYSGOOD #define PTRACE_O_TRACESYSGOOD 1 #endif #ifndef PTRACE_O_TRACEFORK #define PTRACE_O_TRACEFORK 2 #endif #ifndef PTRACE_O_TRACEVFORK #define PTRACE_O_TRACEVFORK 4 #endif #ifndef PTRACE_O_TRACECLONE #define PTRACE_O_TRACECLONE 8 #endif #ifndef O_DIRECT #define O_DIRECT 040000 #endif static void sanitize_child(); static int get_action(struct pt_sandbox* p_sandbox); static int validate_mmap2(struct pt_sandbox* p_sandbox, void* p_arg); static int validate_open_default(struct pt_sandbox* p_sandbox, void* p_arg); static int validate_open_readonly(struct pt_sandbox* p_sandbox, void* p_arg); static int validate_fcntl(struct pt_sandbox* p_sandbox, void* p_arg); static int validate_socketcall(struct pt_sandbox* p_sandbox, void* p_arg); static void install_socketcall(struct pt_sandbox* p_sandbox); #define MAX_SYSCALL 300 struct pt_sandbox { int read_event_fd; int write_event_fd; pid_t pid; int is_allowed[MAX_SYSCALL]; ptrace_sandbox_validator_t validator[MAX_SYSCALL]; void* validator_arg[MAX_SYSCALL]; int is_exit; struct user_regs_struct regs; int is_socketcall_allowed[NPROTO]; ptrace_sandbox_validator_t socketcall_validator[NPROTO]; void* socketcall_validator_arg[NPROTO]; }; static int s_sigchld_fd = -1; void handle_sigchld(int sig) { int ret; if (sig != SIGCHLD) { _exit(1); } if (s_sigchld_fd != -1) { do { static const char zero = '\0'; ret = write(s_sigchld_fd, &zero, sizeof(zero)); } while (ret == -1 && errno == EINTR); if (ret != 1) { _exit(2); } } } struct pt_sandbox* ptrace_sandbox_alloc() { int i; struct sigaction sigact; struct pt_sandbox* ret = malloc(sizeof(struct pt_sandbox)); if (ret == NULL) { return NULL; } ret->pid = -1; ret->read_event_fd = -1; ret->write_event_fd = -1; ret->is_exit = 0; memset(&ret->regs, '\0', sizeof(ret->regs)); for (i = 0; i < MAX_SYSCALL; ++i) { ret->is_allowed[i] = 0; ret->validator[i] = 0; ret->validator_arg[i] = 0; } for (i = 0; i < NPROTO; ++i) { ret->is_socketcall_allowed[i] = 0; ret->socketcall_validator[i] = 0; ret->socketcall_validator_arg[i] = 0; } memset((void*) &sigact, '\0', sizeof(sigact)); sigact.sa_handler = handle_sigchld; if (sigaction(SIGCHLD, &sigact, NULL) != 0) { goto err_out; } return ret; err_out: ptrace_sandbox_free(ret); return NULL; } void ptrace_sandbox_free(struct pt_sandbox* p_sandbox) { if (p_sandbox->pid != -1) { warnx("bug: pid active in ptrace_sandbox_free"); /* We'll kill it for you so it doesn't escape the sandbox totally, but * we won't reap the zombie. * Killing it like this is a risk: if it's stopped in syscall entry, * that syscall will execute before the pending kill takes effect. * If that pending syscall were to be a fork(), there could be trouble. */ (void) kill(p_sandbox->pid, SIGKILL); } if (p_sandbox->read_event_fd != -1) { s_sigchld_fd = -1; close(p_sandbox->read_event_fd); close(p_sandbox->write_event_fd); } free(p_sandbox); } void ptrace_sandbox_attach_point() { long pt_ret; int ret; pid_t pid = getpid(); if (pid <= 1) { warnx("weird pid"); _exit(1); } /* You don't have to use PTRACE_TRACEME, but if you don't, a rogue SIGCONT * might wake you up from the STOP below before the tracer has attached. */ pt_ret = ptrace(PTRACE_TRACEME, 0, 0, 0); if (pt_ret != 0) { warn("PTRACE_TRACEME failed"); _exit(2); } ret = kill(pid, SIGSTOP); if (ret != 0) { warn("kill SIGSTOP failed"); _exit(3); } } int ptrace_sandbox_launch_process(struct pt_sandbox* p_sandbox, void (*p_func)(void*), void* p_arg) { long pt_ret; pid_t ret; int status; if (p_sandbox->pid != -1) { warnx("bug: process already active"); return -1; } ret = fork(); if (ret < 0) { return -1; } else if (ret == 0) { /* Child context. */ sanitize_child(); (*p_func)(p_arg); _exit(0); } /* Parent context */ p_sandbox->pid = ret; do { ret = waitpid(p_sandbox->pid, &status, 0); } while (ret == -1 && errno == EINTR); if (ret == -1) { warn("waitpid failed"); goto kill_out; } else if (ret != p_sandbox->pid) { warnx("unknown pid %d", ret); goto kill_out; } if (!WIFSTOPPED(status)) { warnx("not stopped status %d\n", status); goto kill_out; } if (WSTOPSIG(status) != SIGSTOP) { warnx("not SIGSTOP status %d\n", status); goto kill_out; } /* The fork, etc. tracing options are worth a bit of explanation. We don't * permit process launching syscalls at all as they are dangerous. But * there's a small race if the untrusted process attempts a denied fork() * and then takes a rouge SIGKILL before the supervisor gets a chance to * clear the orig_eax register. In this case the syscall will still execute. * (Policies may not include signal sending capabilities, thus mitigating this * direct attack, however a rogue SIGKILL may come from a non-malicious * source). Therefore, we'd rather any fork()ed process starts off traced, * just in case this tiny race condition triggers. */ pt_ret = ptrace(PTRACE_SETOPTIONS, p_sandbox->pid, 0, PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACEFORK | PTRACE_O_TRACEVFORK | PTRACE_O_TRACECLONE); if (pt_ret != 0) { warn("PTRACE_SETOPTIONS failure"); goto kill_out; } return p_sandbox->pid; kill_out: (void) kill(p_sandbox->pid, SIGKILL); p_sandbox->pid = -1; return -1; } int ptrace_sandbox_continue_process(struct pt_sandbox* p_sandbox, int sig) { long pt_ret = ptrace(PTRACE_SYSCALL, p_sandbox->pid, 0, sig); if (pt_ret != 0) { warn("PTRACE_SYSCALL failure"); if (errno == ESRCH) { return PTRACE_SANDBOX_ERR_DEAD; } return PTRACE_SANDBOX_ERR_PTRACE; } return 0; } int ptrace_sandbox_get_event_fd(struct pt_sandbox* p_sandbox) { /* TODO: allocate pipe fds */ (void) p_sandbox; return -1; } int ptrace_sandbox_get_event(struct pt_sandbox* p_sandbox, int* status, int block) { pid_t pid; int options = 0; if (!block) { options = WNOHANG; } do { pid = waitpid(p_sandbox->pid, status, options); } while (pid == -1 && errno == EINTR); if (pid == -1) { warn("waitpid failure"); if (errno == ECHILD) { return PTRACE_SANDBOX_ERR_DEAD; } return PTRACE_SANDBOX_ERR_WAITPID; } return pid; } int ptrace_sandbox_handle_event(struct pt_sandbox* p_sandbox, int status) { int sig; int action; if (WIFEXITED(status) || WIFSIGNALED(status)) { p_sandbox->pid = -1; return 1; } if (!WIFSTOPPED(status)) { warnx("weird status: %d\n", status); return PTRACE_SANDBOX_ERR_WAIT_STATUS; } sig = WSTOPSIG(status); if (sig >= 0 && sig < 0x80) { /* It's a normal signal; deliver it right on. SIGSTOP / SIGCONT handling * are buggy in the kernel and I'm not sure it's safe to pass either on, * so the signal becomes a little more... robust :) */ if (sig == SIGSTOP || sig == SIGCONT) { sig = SIGKILL; } return ptrace_sandbox_continue_process(p_sandbox, sig); } if (!(sig & 0x80)) { warnx("weird status: %d\n", status); return PTRACE_SANDBOX_ERR_WAIT_STATUS; } /* Syscall trap. */ if (p_sandbox->is_exit) { p_sandbox->is_exit = 0; } else { p_sandbox->is_exit = 1; action = get_action(p_sandbox); if (action != 0) { return action; } } return ptrace_sandbox_continue_process(p_sandbox, 0); } int ptrace_sandbox_run_processes(struct pt_sandbox* p_sandbox) { if (ptrace_sandbox_continue_process(p_sandbox, 0) != 0) { goto kill_out; } while (1) { int status; int ret = ptrace_sandbox_get_event(p_sandbox, &status, 1); if (ret <= 0) { goto kill_out; } ret = ptrace_sandbox_handle_event(p_sandbox, status); if (ret < 0) { warnx("couldn't handle sandbox event"); goto kill_out; } if (ret == 1) { return 0; } } kill_out: ptrace_sandbox_kill_processes(p_sandbox); return -1; } void ptrace_sandbox_kill_processes(struct pt_sandbox* p_sandbox) { long pt_ret; struct user_regs_struct regs; pid_t pid = p_sandbox->pid; if (pid == -1) { return; } p_sandbox->pid = -1; pt_ret = ptrace(PTRACE_GETREGS, pid, 0, ®s); if (pt_ret != 0) { warn("PTRACE_GETREGS failure"); /* This API is supposed to be called with the process stopped; but if it * is still running, we can at least help a bit. See security related * comment in ptrace_sandbox_free(), though. */ (void) kill(pid, SIGKILL); return; } /* Kind of nasty, but the only way of stopping a started syscall from * executing is to rewrite the registers to execute a different syscall. */ regs.orig_eax = __NR_exit_group; regs.eip = 0xffffffff; pt_ret = ptrace(PTRACE_SETREGS, pid, 0, ®s); if (pt_ret != 0) { warn("PTRACE_SETREGS failure"); /* Deliberate fall-thru. */ } pt_ret = ptrace(PTRACE_KILL, pid, 0, 0); if (pt_ret != 0) { warn("PTRACE_KILL failure"); /* Deliberate fall-thru. */ } /* Just to make ourselves clear. */ (void) kill(pid, SIGKILL); /* So the GETREGS succeeded, so the process definitely _was_ there. We can * safely wait for it to reap the zombie. */ (void) waitpid(pid, NULL, 0); } int ptrace_sandbox_get_arg(struct pt_sandbox* p_sandbox, int arg, unsigned long* p_out) { long ret = 0; struct user_regs_struct* p_regs = &p_sandbox->regs; if (p_regs->orig_eax == 0) { return PTRACE_SANDBOX_ERR_API_ABUSE_STOPIT; } if (arg < 0 || arg > 5) { return PTRACE_SANDBOX_ERR_API_ABUSE_STOPIT; } switch (arg) { case 0: ret = p_regs->ebx; break; case 1: ret = p_regs->ecx; break; case 2: ret = p_regs->edx; break; case 3: ret = p_regs->esi; break; case 4: ret = p_regs->edi; break; case 5: ret = p_regs->ebp; break; } *p_out = ret; return 0; } int ptrace_sandbox_get_socketcall_arg(struct pt_sandbox* p_sandbox, int arg, unsigned long* p_out) { unsigned long ptr; int ret; struct user_regs_struct* p_regs = &p_sandbox->regs; if (p_regs->orig_eax == 0) { return PTRACE_SANDBOX_ERR_API_ABUSE_STOPIT; } if (arg < 0 || arg > 2) { return PTRACE_SANDBOX_ERR_API_ABUSE_STOPIT; } ret = ptrace_sandbox_get_arg(p_sandbox, 1, &ptr); if (ret != 0) { return ret; } ptr += (arg * 4); ret = ptrace_sandbox_get_long(p_sandbox, ptr, p_out); return ret; } int ptrace_sandbox_get_long(struct pt_sandbox* p_sandbox, unsigned long ptr, unsigned long* p_out) { return ptrace_sandbox_get_buf(p_sandbox, ptr, sizeof(long), (void*) p_out); } int ptrace_sandbox_get_buf(struct pt_sandbox* p_sandbox, unsigned long ptr, unsigned long len, void* p_buf) { long pt_ret; char* p_out = (char*) p_buf; for (; len > 0; len -= sizeof(long)) { errno = 0; pt_ret = ptrace(PTRACE_PEEKDATA, p_sandbox->pid, (void*) ptr, 0); if (pt_ret == -1 && errno != 0) { warn("PTRACE_GETREGS failure"); if (errno == ESRCH) { return PTRACE_SANDBOX_ERR_DEAD; } return PTRACE_SANDBOX_ERR_PTRACE; } if (len >= sizeof(long)) { memcpy(p_out, &pt_ret, sizeof(long)); } else { memcpy(p_out, &pt_ret, len); } p_out += sizeof(long); ptr += sizeof(long); } return 0; } static void sanitize_child() { /* Ensure that if our sandbox supervisor goes down, so do we. */ int ret = prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0); if (ret != 0) { _exit(3); } } static int get_action(struct pt_sandbox* p_sandbox) { int ret; int call; int cs; long pt_ret = ptrace(PTRACE_GETREGS, p_sandbox->pid, 0, &(p_sandbox->regs)); if (pt_ret != 0) { warn("PTRACE_GETREGS failure"); if (errno == ESRCH) { return PTRACE_SANDBOX_ERR_DEAD; } return PTRACE_SANDBOX_ERR_PTRACE; } /* We need to be sure that the child is attempting a syscall against the * 32-bit syscall table, otherwise they can bypass the policy by abusing the * fact that e.g. syscall 200 is getgid32() on 32-bit but tkill() on 64-bit. * If the syscall instruct was int80 or sysenter, is it guaranteed to hit * the 32-bit table. If it is syscall, the current CS selector determines * the table. Therefore, we can check the current CS selector references a * known system-only selector that is guaranteed 32-bit (not long mode). */ cs = p_sandbox->regs.xcs; if (cs != 0x73 && cs != 0x23) { warnx("bad CS %d", cs); ret = PTRACE_SANDBOX_ERR_BAD_SYSCALL; goto out; } call = (int) p_sandbox->regs.orig_eax; if (call < 0 || call >= MAX_SYSCALL) { warnx("syscall %d out of bounds", call); ret = PTRACE_SANDBOX_ERR_BAD_SYSCALL; goto out; } if (p_sandbox->is_allowed[call] != 1) { syslog(LOG_LOCAL0 | LOG_DEBUG, "syscall not permitted: %d", call); warnx("syscall not permitted: %d", call); ret = PTRACE_SANDBOX_ERR_POLICY_SYSCALL; goto out; } if (p_sandbox->validator[call]) { ptrace_sandbox_validator_t p_validate = p_sandbox->validator[call]; int validate_ret = (*p_validate)(p_sandbox, p_sandbox->validator_arg[call]); if (validate_ret != 0) { syslog(LOG_LOCAL0 | LOG_DEBUG, "syscall validate fail: %d (%d)", call, validate_ret); warnx("syscall validate failed: %d (%d)", call, validate_ret); ret = PTRACE_SANDBOX_ERR_POLICY_ARGS; goto out; } } ret = 0; out: memset(&p_sandbox->regs, '\0', sizeof(p_sandbox->regs)); return ret; } void ptrace_sandbox_permit_exit(struct pt_sandbox* p_sandbox) { p_sandbox->is_allowed[__NR_exit] = 1; p_sandbox->is_allowed[__NR_exit_group] = 1; } void ptrace_sandbox_permit_read(struct pt_sandbox* p_sandbox) { p_sandbox->is_allowed[__NR_read] = 1; } void ptrace_sandbox_permit_write(struct pt_sandbox* p_sandbox) { p_sandbox->is_allowed[__NR_write] = 1; } void ptrace_sandbox_permit_sigaction(struct pt_sandbox* p_sandbox) { p_sandbox->is_allowed[__NR_sigaction] = 1; p_sandbox->is_allowed[__NR_rt_sigaction] = 1; } void ptrace_sandbox_permit_alarm(struct pt_sandbox* p_sandbox) { p_sandbox->is_allowed[__NR_alarm] = 1; } void ptrace_sandbox_permit_query_time(struct pt_sandbox* p_sandbox) { p_sandbox->is_allowed[__NR_gettimeofday] = 1; p_sandbox->is_allowed[__NR_time] = 1; } void ptrace_sandbox_permit_mmap(struct pt_sandbox* p_sandbox) { p_sandbox->is_allowed[__NR_mmap2] = 1; p_sandbox->validator[__NR_mmap2] = validate_mmap2; } static int validate_mmap2(struct pt_sandbox* p_sandbox, void* p_arg) { unsigned long arg4; int ret = ptrace_sandbox_get_arg(p_sandbox, 3, &arg4); (void) p_arg; if (ret != 0) { return ret; } if (arg4 & MAP_SHARED) { return -1; } return 0; } void ptrace_sandbox_permit_mprotect(struct pt_sandbox* p_sandbox) { p_sandbox->is_allowed[__NR_mprotect] = 1; } void ptrace_sandbox_permit_file_stats(struct pt_sandbox* p_sandbox) { p_sandbox->is_allowed[__NR_stat] = 1; p_sandbox->is_allowed[__NR_stat64] = 1; p_sandbox->is_allowed[__NR_lstat] = 1; p_sandbox->is_allowed[__NR_lstat64] = 1; } void ptrace_sandbox_permit_fd_stats(struct pt_sandbox* p_sandbox) { p_sandbox->is_allowed[__NR_fstat] = 1; p_sandbox->is_allowed[__NR_fstat64] = 1; } void ptrace_sandbox_permit_getcwd(struct pt_sandbox* p_sandbox) { p_sandbox->is_allowed[__NR_getcwd] = 1; } void ptrace_sandbox_permit_chdir(struct pt_sandbox* p_sandbox) { p_sandbox->is_allowed[__NR_chdir] = 1; } void ptrace_sandbox_permit_umask(struct pt_sandbox* p_sandbox) { p_sandbox->is_allowed[__NR_umask] = 1; } void ptrace_sandbox_permit_open(struct pt_sandbox* p_sandbox, int writeable) { p_sandbox->is_allowed[__NR_open] = 1; if (writeable == 1) { p_sandbox->validator[__NR_open] = validate_open_default; } else { p_sandbox->validator[__NR_open] = validate_open_readonly; } } static int validate_open_default(struct pt_sandbox* p_sandbox, void* p_arg) { unsigned long arg2; int ret = ptrace_sandbox_get_arg(p_sandbox, 1, &arg2); (void) p_arg; if (ret != 0) { return ret; } if (arg2 & (O_ASYNC | O_DIRECT | O_SYNC)) { return -1; } return 0; } static int validate_open_readonly(struct pt_sandbox* p_sandbox, void* p_arg) { unsigned long arg2; int ret = validate_open_default(p_sandbox, p_arg); if (ret != 0) { return ret; } ret = ptrace_sandbox_get_arg(p_sandbox, 1, &arg2); if (ret != 0) { return ret; } if ((arg2 & O_ACCMODE) != O_RDONLY) { return -1; } return 0; } void ptrace_sandbox_permit_close(struct pt_sandbox* p_sandbox) { p_sandbox->is_allowed[__NR_close] = 1; } void ptrace_sandbox_permit_getdents(struct pt_sandbox* p_sandbox) { p_sandbox->is_allowed[__NR_getdents] = 1; p_sandbox->is_allowed[__NR_getdents64] = 1; } void ptrace_sandbox_permit_fcntl(struct pt_sandbox* p_sandbox) { p_sandbox->is_allowed[__NR_fcntl] = 1; p_sandbox->validator[__NR_fcntl] = validate_fcntl; p_sandbox->is_allowed[__NR_fcntl64] = 1; p_sandbox->validator[__NR_fcntl64] = validate_fcntl; } static int validate_fcntl(struct pt_sandbox* p_sandbox, void* p_arg) { unsigned long arg2; unsigned long arg3; int ret = ptrace_sandbox_get_arg(p_sandbox, 1, &arg2); (void) p_arg; if (ret != 0) { return ret; } ret = ptrace_sandbox_get_arg(p_sandbox, 2, &arg3); if (ret != 0) { return ret; } if (arg2 != F_GETFL && arg2 != F_SETFL && arg2 != F_SETOWN && arg2 != F_SETLK && arg2 != F_SETLKW && arg2 != F_SETLK64 && arg2 != F_SETLKW64 && arg2 != F_SETFD && arg2 != F_GETFD) { syslog(LOG_LOCAL0 | LOG_DEBUG, "fcntl not permitted: %ld", arg2); warnx("fcntl not permitted: %ld", arg2); return -1; } if (arg2 == F_SETFL && (arg3 & (O_ASYNC | O_DIRECT))) { return -2; } if (arg2 == F_SETOWN && (int) arg3 != p_sandbox->pid) { return -3; } return 0; } void ptrace_sandbox_permit_sendfile(struct pt_sandbox* p_sandbox) { p_sandbox->is_allowed[__NR_sendfile] = 1; p_sandbox->is_allowed[__NR_sendfile64] = 1; } void ptrace_sandbox_permit_seek(struct pt_sandbox* p_sandbox) { p_sandbox->is_allowed[__NR_lseek] = 1; p_sandbox->is_allowed[__NR__llseek] = 1; } void ptrace_sandbox_permit_select(struct pt_sandbox* p_sandbox) { p_sandbox->is_allowed[__NR_select] = 1; p_sandbox->is_allowed[__NR__newselect] = 1; } void ptrace_sandbox_permit_unlink(struct pt_sandbox* p_sandbox) { p_sandbox->is_allowed[__NR_unlink] = 1; } void ptrace_sandbox_permit_mkdir(struct pt_sandbox* p_sandbox) { p_sandbox->is_allowed[__NR_mkdir] = 1; } void ptrace_sandbox_permit_rmdir(struct pt_sandbox* p_sandbox) { p_sandbox->is_allowed[__NR_rmdir] = 1; } void ptrace_sandbox_permit_rename(struct pt_sandbox* p_sandbox) { p_sandbox->is_allowed[__NR_rename] = 1; } void ptrace_sandbox_permit_utime(struct pt_sandbox* p_sandbox) { p_sandbox->is_allowed[__NR_utime] = 1; p_sandbox->is_allowed[__NR_utimes] = 1; } void ptrace_sandbox_permit_sigreturn(struct pt_sandbox* p_sandbox) { p_sandbox->is_allowed[__NR_sigreturn] = 1; } void ptrace_sandbox_permit_recv(struct pt_sandbox* p_sandbox) { install_socketcall(p_sandbox); p_sandbox->is_socketcall_allowed[SYS_RECV] = 1; } static void install_socketcall(struct pt_sandbox* p_sandbox) { p_sandbox->is_allowed[__NR_socketcall] = 1; p_sandbox->validator[__NR_socketcall] = validate_socketcall; } static int validate_socketcall(struct pt_sandbox* p_sandbox, void* p_arg) { unsigned long arg1; int ret = ptrace_sandbox_get_arg(p_sandbox, 0, &arg1); (void) p_arg; if (ret != 0) { return ret; } if (arg1 < 1 || arg1 >= NPROTO) { return -1; } if (p_sandbox->is_socketcall_allowed[arg1] != 1) { syslog(LOG_LOCAL0 | LOG_DEBUG, "socketcall not permitted: %ld", arg1); warnx("socketcall not permitted: %ld", arg1); return -2; } if (p_sandbox->socketcall_validator[arg1]) { ptrace_sandbox_validator_t p_val = p_sandbox->socketcall_validator[arg1]; ret = (*p_val)(p_sandbox, p_sandbox->socketcall_validator_arg[arg1]); if (ret != 0) { syslog(LOG_LOCAL0 | LOG_DEBUG, "socketcall validate fail: %ld (%d)", arg1, ret); warnx("socketcall validate fail: %ld (%d)", arg1, ret); return -3; } } return 0; } void ptrace_sandbox_permit_readlink(struct pt_sandbox* p_sandbox) { p_sandbox->is_allowed[__NR_readlink] = 1; } void ptrace_sandbox_permit_brk(struct pt_sandbox* p_sandbox) { p_sandbox->is_allowed[__NR_brk] = 1; } void ptrace_sandbox_permit_sleep(struct pt_sandbox* p_sandbox) { p_sandbox->is_allowed[__NR_nanosleep] = 1; } void ptrace_sandbox_permit_fchmod(struct pt_sandbox* p_sandbox) { p_sandbox->is_allowed[__NR_fchmod] = 1; } void ptrace_sandbox_permit_chmod(struct pt_sandbox* p_sandbox) { p_sandbox->is_allowed[__NR_chmod] = 1; } void ptrace_sandbox_permit_fchown(struct pt_sandbox* p_sandbox) { p_sandbox->is_allowed[__NR_fchown] = 1; p_sandbox->is_allowed[__NR_fchown32] = 1; } void ptrace_sandbox_permit_mremap(struct pt_sandbox* p_sandbox) { p_sandbox->is_allowed[__NR_mremap] = 1; } void ptrace_sandbox_permit_ftruncate(struct pt_sandbox* p_sandbox) { p_sandbox->is_allowed[__NR_ftruncate] = 1; p_sandbox->is_allowed[__NR_ftruncate64] = 1; } void ptrace_sandbox_permit_socket(struct pt_sandbox* p_sandbox) { install_socketcall(p_sandbox); p_sandbox->is_socketcall_allowed[SYS_SOCKET] = 1; } void ptrace_sandbox_set_socket_validator(struct pt_sandbox* p_sandbox, ptrace_sandbox_validator_t val, void* p_arg) { p_sandbox->socketcall_validator[SYS_SOCKET] = val; p_sandbox->socketcall_validator_arg[SYS_SOCKET] = p_arg; } void ptrace_sandbox_permit_bind(struct pt_sandbox* p_sandbox) { install_socketcall(p_sandbox); p_sandbox->is_socketcall_allowed[SYS_BIND] = 1; } void ptrace_sandbox_set_bind_validator(struct pt_sandbox* p_sandbox, ptrace_sandbox_validator_t val, void* p_arg) { p_sandbox->socketcall_validator[SYS_BIND] = val; p_sandbox->socketcall_validator_arg[SYS_BIND] = p_arg; } void ptrace_sandbox_permit_connect(struct pt_sandbox* p_sandbox) { install_socketcall(p_sandbox); p_sandbox->is_socketcall_allowed[SYS_CONNECT] = 1; } void ptrace_sandbox_set_connect_validator(struct pt_sandbox* p_sandbox, ptrace_sandbox_validator_t val, void* p_arg) { p_sandbox->socketcall_validator[SYS_CONNECT] = val; p_sandbox->socketcall_validator_arg[SYS_CONNECT] = p_arg; } void ptrace_sandbox_permit_listen(struct pt_sandbox* p_sandbox) { install_socketcall(p_sandbox); p_sandbox->is_socketcall_allowed[SYS_LISTEN] = 1; } void ptrace_sandbox_permit_accept(struct pt_sandbox* p_sandbox) { install_socketcall(p_sandbox); p_sandbox->is_socketcall_allowed[SYS_ACCEPT] = 1; } void ptrace_sandbox_permit_setsockopt(struct pt_sandbox* p_sandbox) { install_socketcall(p_sandbox); p_sandbox->is_socketcall_allowed[SYS_SETSOCKOPT] = 1; } void ptrace_sandbox_set_setsockopt_validator(struct pt_sandbox* p_sandbox, ptrace_sandbox_validator_t val, void* p_arg) { p_sandbox->socketcall_validator[SYS_SETSOCKOPT] = val; p_sandbox->socketcall_validator_arg[SYS_SETSOCKOPT] = p_arg; } void ptrace_sandbox_permit_getsockopt(struct pt_sandbox* p_sandbox) { install_socketcall(p_sandbox); p_sandbox->is_socketcall_allowed[SYS_GETSOCKOPT] = 1; } void ptrace_sandbox_set_getsockopt_validator(struct pt_sandbox* p_sandbox, ptrace_sandbox_validator_t val, void* p_arg) { p_sandbox->socketcall_validator[SYS_GETSOCKOPT] = val; p_sandbox->socketcall_validator_arg[SYS_GETSOCKOPT] = p_arg; } void ptrace_sandbox_permit_shutdown(struct pt_sandbox* p_sandbox) { install_socketcall(p_sandbox); p_sandbox->is_socketcall_allowed[SYS_SHUTDOWN] = 1; } #else /* __linux__ && __i386__ */ struct pt_sandbox* ptrace_sandbox_alloc() { return 0; } void ptrace_sandbox_free(struct pt_sandbox* p_sandbox) { (void) p_sandbox; } int ptrace_sandbox_launch_process(struct pt_sandbox* p_sandbox, void (*p_func)(void*), void* p_arg) { (void) p_sandbox; (void) p_func; (void) p_arg; return -1; } int ptrace_sandbox_run_processes(struct pt_sandbox* p_sandbox) { (void) p_sandbox; return -1; } void ptrace_sandbox_attach_point(void) { } void ptrace_sandbox_permit_exit(struct pt_sandbox* p_sandbox) { (void) p_sandbox; } void ptrace_sandbox_permit_read(struct pt_sandbox* p_sandbox) { (void) p_sandbox; } void ptrace_sandbox_permit_write(struct pt_sandbox* p_sandbox) { (void) p_sandbox; } void ptrace_sandbox_permit_sigaction(struct pt_sandbox* p_sandbox) { (void) p_sandbox; } void ptrace_sandbox_permit_alarm(struct pt_sandbox* p_sandbox) { (void) p_sandbox; } void ptrace_sandbox_permit_query_time(struct pt_sandbox* p_sandbox) { (void) p_sandbox; } void ptrace_sandbox_permit_mmap(struct pt_sandbox* p_sandbox) { (void) p_sandbox; } void ptrace_sandbox_permit_mprotect(struct pt_sandbox* p_sandbox) { (void) p_sandbox; } void ptrace_sandbox_permit_file_stats(struct pt_sandbox* p_sandbox) { (void) p_sandbox; } void ptrace_sandbox_permit_fd_stats(struct pt_sandbox* p_sandbox) { (void) p_sandbox; } void ptrace_sandbox_permit_getcwd(struct pt_sandbox* p_sandbox) { (void) p_sandbox; } void ptrace_sandbox_permit_chdir(struct pt_sandbox* p_sandbox) { (void) p_sandbox; } void ptrace_sandbox_permit_umask(struct pt_sandbox* p_sandbox) { (void) p_sandbox; } void ptrace_sandbox_permit_open(struct pt_sandbox* p_sandbox, int writeable) { (void) p_sandbox; (void) writeable; } void ptrace_sandbox_permit_close(struct pt_sandbox* p_sandbox) { (void) p_sandbox; } void ptrace_sandbox_permit_getdents(struct pt_sandbox* p_sandbox) { (void) p_sandbox; } void ptrace_sandbox_permit_fcntl(struct pt_sandbox* p_sandbox) { (void) p_sandbox; } void ptrace_sandbox_permit_sendfile(struct pt_sandbox* p_sandbox) { (void) p_sandbox; } void ptrace_sandbox_permit_seek(struct pt_sandbox* p_sandbox) { (void) p_sandbox; } void ptrace_sandbox_permit_select(struct pt_sandbox* p_sandbox) { (void) p_sandbox; } void ptrace_sandbox_permit_unlink(struct pt_sandbox* p_sandbox) { (void) p_sandbox; } void ptrace_sandbox_permit_mkdir(struct pt_sandbox* p_sandbox) { (void) p_sandbox; } void ptrace_sandbox_permit_rmdir(struct pt_sandbox* p_sandbox) { (void) p_sandbox; } void ptrace_sandbox_permit_rename(struct pt_sandbox* p_sandbox) { (void) p_sandbox; } void ptrace_sandbox_permit_utime(struct pt_sandbox* p_sandbox) { (void) p_sandbox; } void ptrace_sandbox_permit_utimes(struct pt_sandbox* p_sandbox) { (void) p_sandbox; } void ptrace_sandbox_permit_sigreturn(struct pt_sandbox* p_sandbox) { (void) p_sandbox; } void ptrace_sandbox_permit_recv(struct pt_sandbox* p_sandbox) { (void) p_sandbox; } void ptrace_sandbox_kill_processes(struct pt_sandbox* p_sandbox) { (void) p_sandbox; } int ptrace_sandbox_get_arg(struct pt_sandbox* p_sandbox, int arg, unsigned long* p_out) { (void) p_sandbox; (void) arg; (void) p_out; return -1; } int ptrace_sandbox_get_socketcall_arg(struct pt_sandbox* p_sandbox, int arg, unsigned long* p_out) { (void) p_sandbox; (void) arg; (void) p_out; return -1; } int ptrace_sandbox_get_long(struct pt_sandbox* p_sandbox, unsigned long ptr, unsigned long* p_out) { (void) p_sandbox; (void) ptr; (void) p_out; return -1; } int ptrace_sandbox_get_buf(struct pt_sandbox* p_sandbox, unsigned long ptr, unsigned long len, void* p_buf) { (void) p_sandbox; (void) ptr; (void) len; (void) p_buf; return -1; } void ptrace_sandbox_permit_readlink(struct pt_sandbox* p_sandbox) { (void) p_sandbox; } void ptrace_sandbox_permit_brk(struct pt_sandbox* p_sandbox) { (void) p_sandbox; } void ptrace_sandbox_permit_sleep(struct pt_sandbox* p_sandbox) { (void) p_sandbox; } void ptrace_sandbox_permit_fchmod(struct pt_sandbox* p_sandbox) { (void) p_sandbox; } void ptrace_sandbox_permit_chmod(struct pt_sandbox* p_sandbox) { (void) p_sandbox; } void ptrace_sandbox_permit_fchown(struct pt_sandbox* p_sandbox) { (void) p_sandbox; } void ptrace_sandbox_permit_mremap(struct pt_sandbox* p_sandbox) { (void) p_sandbox; } void ptrace_sandbox_permit_ftruncate(struct pt_sandbox* p_sandbox) { (void) p_sandbox; } void ptrace_sandbox_permit_socket(struct pt_sandbox* p_sandbox) { (void) p_sandbox; } void ptrace_sandbox_set_socket_validator(struct pt_sandbox* p_sandbox, ptrace_sandbox_validator_t val, void* p_arg) { (void) p_sandbox; (void) val; (void) p_arg; } void ptrace_sandbox_permit_bind(struct pt_sandbox* p_sandbox) { (void) p_sandbox; } void ptrace_sandbox_set_bind_validator(struct pt_sandbox* p_sandbox, ptrace_sandbox_validator_t val, void* p_arg) { (void) p_sandbox; (void) val; (void) p_arg; } void ptrace_sandbox_permit_connect(struct pt_sandbox* p_sandbox) { (void) p_sandbox; } void ptrace_sandbox_set_connect_validator(struct pt_sandbox* p_sandbox, ptrace_sandbox_validator_t val, void* p_arg) { (void) p_sandbox; (void) val; (void) p_arg; } void ptrace_sandbox_permit_listen(struct pt_sandbox* p_sandbox) { (void) p_sandbox; } void ptrace_sandbox_permit_accept(struct pt_sandbox* p_sandbox) { (void) p_sandbox; } void ptrace_sandbox_permit_setsockopt(struct pt_sandbox* p_sandbox) { (void) p_sandbox; } void ptrace_sandbox_set_setsockopt_validator(struct pt_sandbox* p_sandbox, ptrace_sandbox_validator_t val, void* p_arg) { (void) p_sandbox; (void) val; (void) p_arg; } void ptrace_sandbox_permit_getsockopt(struct pt_sandbox* p_sandbox) { (void) p_sandbox; } void ptrace_sandbox_set_getsockopt_validator(struct pt_sandbox* p_sandbox, ptrace_sandbox_validator_t val, void* p_arg) { (void) p_sandbox; (void) val; (void) p_arg; } void ptrace_sandbox_permit_shutdown(struct pt_sandbox* p_sandbox) { (void) p_sandbox; } #endif /* __linux__ && __i386__ */ vsftpd-3.0.3/tcpwrap.h0000644000175000017500000000016110750743677013674 0ustar chrischris#ifndef VSF_TCPWRAP_H #define VSF_TCPWRAP_H int vsf_tcp_wrapper_ok(int remote_fd); #endif /* VSF_TCPWRAP_H */ vsftpd-3.0.3/ssl.c0000644000175000017500000004773112554065014013010 0ustar chrischris/* * Part of Very Secure FTPd * Licence: GPL v2. Note that this code interfaces with with the OpenSSL * libraries, so please read LICENSE where I give explicit permission to link * against the OpenSSL libraries. * Author: Chris Evans * ssl.c * * Routines to handle a SSL/TLS-based implementation of RFC 2228, i.e. * encryption. */ #include "ssl.h" #include "session.h" #include "ftpcodes.h" #include "ftpcmdio.h" #include "defs.h" #include "str.h" #include "sysutil.h" #include "tunables.h" #include "utility.h" #include "builddefs.h" #include "logging.h" #ifdef VSF_BUILD_SSL #include #include #include #include #include #include static char* get_ssl_error(); static SSL* get_ssl(struct vsf_session* p_sess, int fd); static int ssl_session_init(struct vsf_session* p_sess); static void setup_bio_callbacks(); static long bio_callback( BIO* p_bio, int oper, const char* p_arg, int argi, long argl, long retval); static int ssl_verify_callback(int verify_ok, X509_STORE_CTX* p_ctx); static int ssl_cert_digest( SSL* p_ssl, struct vsf_session* p_sess, struct mystr* p_str); static void maybe_log_shutdown_state(struct vsf_session* p_sess); static void maybe_log_ssl_error_state(struct vsf_session* p_sess, int ret); static int ssl_read_common(struct vsf_session* p_sess, SSL* p_ssl, char* p_buf, unsigned int len, int (*p_ssl_func)(SSL*, void*, int)); static int ssl_inited; static struct mystr debug_str; void ssl_init(struct vsf_session* p_sess) { if (!ssl_inited) { SSL_CTX* p_ctx; long options; int verify_option = 0; SSL_library_init(); p_ctx = SSL_CTX_new(SSLv23_server_method()); if (p_ctx == NULL) { die("SSL: could not allocate SSL context"); } options = SSL_OP_ALL; if (!tunable_sslv2) { options |= SSL_OP_NO_SSLv2; } if (!tunable_sslv3) { options |= SSL_OP_NO_SSLv3; } if (!tunable_tlsv1) { options |= SSL_OP_NO_TLSv1; } SSL_CTX_set_options(p_ctx, options); if (tunable_rsa_cert_file) { const char* p_key = tunable_rsa_private_key_file; if (!p_key) { p_key = tunable_rsa_cert_file; } if (SSL_CTX_use_certificate_chain_file(p_ctx, tunable_rsa_cert_file) != 1) { die("SSL: cannot load RSA certificate"); } if (SSL_CTX_use_PrivateKey_file(p_ctx, p_key, X509_FILETYPE_PEM) != 1) { die("SSL: cannot load RSA private key"); } } if (tunable_dsa_cert_file) { const char* p_key = tunable_dsa_private_key_file; if (!p_key) { p_key = tunable_dsa_cert_file; } if (SSL_CTX_use_certificate_chain_file(p_ctx, tunable_dsa_cert_file) != 1) { die("SSL: cannot load DSA certificate"); } if (SSL_CTX_use_PrivateKey_file(p_ctx, p_key, X509_FILETYPE_PEM) != 1) { die("SSL: cannot load DSA private key"); } } if (tunable_ssl_ciphers && SSL_CTX_set_cipher_list(p_ctx, tunable_ssl_ciphers) != 1) { die("SSL: could not set cipher list"); } if (RAND_status() != 1) { die("SSL: RNG is not seeded"); } { EC_KEY* key = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); if (key == NULL) { die("SSL: failed to get curve p256"); } SSL_CTX_set_tmp_ecdh(p_ctx, key); EC_KEY_free(key); } if (tunable_ssl_request_cert) { verify_option |= SSL_VERIFY_PEER; } if (tunable_require_cert) { verify_option |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT; } if (verify_option) { SSL_CTX_set_verify(p_ctx, verify_option, ssl_verify_callback); if (tunable_ca_certs_file) { STACK_OF(X509_NAME)* p_names; if (!SSL_CTX_load_verify_locations(p_ctx, tunable_ca_certs_file, NULL)) { die("SSL: could not load verify file"); } p_names = SSL_load_client_CA_file(tunable_ca_certs_file); if (!p_names) { die("SSL: could not load client certs file"); } SSL_CTX_set_client_CA_list(p_ctx, p_names); } } { static const char* p_ctx_id = "vsftpd"; SSL_CTX_set_session_id_context(p_ctx, (void*) p_ctx_id, vsf_sysutil_strlen(p_ctx_id)); } if (tunable_require_ssl_reuse) { /* Ensure cached session doesn't expire */ SSL_CTX_set_timeout(p_ctx, INT_MAX); } p_sess->p_ssl_ctx = p_ctx; ssl_inited = 1; } } void ssl_control_handshake(struct vsf_session* p_sess) { if (!ssl_session_init(p_sess)) { struct mystr err_str = INIT_MYSTR; str_alloc_text(&err_str, "Negotiation failed: "); /* Technically, we shouldn't leak such detailed error messages. */ str_append_text(&err_str, get_ssl_error()); vsf_cmdio_write_str(p_sess, FTP_TLS_FAIL, &err_str); vsf_sysutil_exit(1); } p_sess->control_use_ssl = 1; } void handle_auth(struct vsf_session* p_sess) { str_upper(&p_sess->ftp_arg_str); if (str_equal_text(&p_sess->ftp_arg_str, "TLS") || str_equal_text(&p_sess->ftp_arg_str, "TLS-C") || str_equal_text(&p_sess->ftp_arg_str, "SSL") || str_equal_text(&p_sess->ftp_arg_str, "TLS-P")) { vsf_cmdio_write(p_sess, FTP_AUTHOK, "Proceed with negotiation."); ssl_control_handshake(p_sess); if (str_equal_text(&p_sess->ftp_arg_str, "SSL") || str_equal_text(&p_sess->ftp_arg_str, "TLS-P")) { p_sess->data_use_ssl = 1; } } else { vsf_cmdio_write(p_sess, FTP_BADAUTH, "Unknown AUTH type."); } } void handle_pbsz(struct vsf_session* p_sess) { if (!p_sess->control_use_ssl) { vsf_cmdio_write(p_sess, FTP_BADPBSZ, "PBSZ needs a secure connection."); } else { vsf_cmdio_write(p_sess, FTP_PBSZOK, "PBSZ set to 0."); } } void handle_prot(struct vsf_session* p_sess) { str_upper(&p_sess->ftp_arg_str); if (!p_sess->control_use_ssl) { vsf_cmdio_write(p_sess, FTP_BADPROT, "PROT needs a secure connection."); } else if (str_equal_text(&p_sess->ftp_arg_str, "C")) { p_sess->data_use_ssl = 0; vsf_cmdio_write(p_sess, FTP_PROTOK, "PROT now Clear."); } else if (str_equal_text(&p_sess->ftp_arg_str, "P")) { p_sess->data_use_ssl = 1; vsf_cmdio_write(p_sess, FTP_PROTOK, "PROT now Private."); } else if (str_equal_text(&p_sess->ftp_arg_str, "S") || str_equal_text(&p_sess->ftp_arg_str, "E")) { vsf_cmdio_write(p_sess, FTP_NOHANDLEPROT, "PROT not supported."); } else { vsf_cmdio_write(p_sess, FTP_NOSUCHPROT, "PROT not recognized."); } } int ssl_read(struct vsf_session* p_sess, void* p_ssl, char* p_buf, unsigned int len) { return ssl_read_common(p_sess, (SSL*) p_ssl, p_buf, len, SSL_read); } int ssl_peek(struct vsf_session* p_sess, void* p_ssl, char* p_buf, unsigned int len) { return ssl_read_common(p_sess, (SSL*) p_ssl, p_buf, len, SSL_peek); } static int ssl_read_common(struct vsf_session* p_sess, SSL* p_void_ssl, char* p_buf, unsigned int len, int (*p_ssl_func)(SSL*, void*, int)) { int retval; int err; SSL* p_ssl = (SSL*) p_void_ssl; do { retval = (*p_ssl_func)(p_ssl, p_buf, len); err = SSL_get_error(p_ssl, retval); } while (retval < 0 && (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE)); /* If we hit an EOF, make sure it was from the peer, not injected by the * attacker. */ if (retval == 0 && SSL_get_shutdown(p_ssl) != SSL_RECEIVED_SHUTDOWN) { if (p_ssl == p_sess->p_control_ssl) { str_alloc_text(&debug_str, "Control"); } else { str_alloc_text(&debug_str, "DATA"); } str_append_text(&debug_str, " connection terminated without SSL shutdown."); if (p_ssl != p_sess->p_control_ssl) { str_append_text(&debug_str, " Buggy client! Integrity of upload cannot be asserted."); } vsf_log_line(p_sess, kVSFLogEntryDebug, &debug_str); if (tunable_strict_ssl_read_eof) { return -1; } } return retval; } int ssl_write(void* p_ssl, const char* p_buf, unsigned int len) { int retval; int err; do { retval = SSL_write((SSL*) p_ssl, p_buf, len); err = SSL_get_error((SSL*) p_ssl, retval); } while (retval < 0 && (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE)); return retval; } int ssl_write_str(void* p_ssl, const struct mystr* p_str) { unsigned int len = str_getlen(p_str); int ret = SSL_write((SSL*) p_ssl, str_getbuf(p_str), len); if ((unsigned int) ret != len) { return -1; } return 0; } int ssl_read_into_str(struct vsf_session* p_sess, void* p_ssl, struct mystr* p_str) { unsigned int len = str_getlen(p_str); int ret = ssl_read(p_sess, p_ssl, (char*) str_getbuf(p_str), len); if (ret >= 0) { str_trunc(p_str, (unsigned int) ret); } else { str_empty(p_str); } return ret; } static void maybe_log_shutdown_state(struct vsf_session* p_sess) { if (tunable_debug_ssl) { int ret = SSL_get_shutdown(p_sess->p_data_ssl); str_alloc_text(&debug_str, "SSL shutdown state is: "); if (ret == 0) { str_append_text(&debug_str, "NONE"); } else if (ret == SSL_SENT_SHUTDOWN) { str_append_text(&debug_str, "SSL_SENT_SHUTDOWN"); } else if (ret == SSL_RECEIVED_SHUTDOWN) { str_append_text(&debug_str, "SSL_RECEIVED_SHUTDOWN"); } else { str_append_ulong(&debug_str, ret); } vsf_log_line(p_sess, kVSFLogEntryDebug, &debug_str); } } static void maybe_log_ssl_error_state(struct vsf_session* p_sess, int ret) { if (tunable_debug_ssl) { str_alloc_text(&debug_str, "SSL ret: "); str_append_ulong(&debug_str, ret); str_append_text(&debug_str, ", SSL error: "); str_append_text(&debug_str, get_ssl_error()); str_append_text(&debug_str, ", errno: "); str_append_ulong(&debug_str, errno); vsf_log_line(p_sess, kVSFLogEntryDebug, &debug_str); } } int ssl_data_close(struct vsf_session* p_sess) { int success = 1; SSL* p_ssl = p_sess->p_data_ssl; if (p_ssl) { int ret; maybe_log_shutdown_state(p_sess); /* Disable Nagle algorithm. We want the shutdown packet to be sent * immediately, there's nothing coming after. */ vsf_sysutil_set_nodelay(SSL_get_fd(p_ssl)); /* This is a mess. Ideally, when we're the sender, we'd like to get to the * SSL_RECEIVED_SHUTDOWN state to get a cryptographic guarantee that the * peer received all the data and shut the connection down cleanly. It * doesn't matter hugely apart from logging, but it's a nagging detail. * Unfortunately, no FTP client I found was able to get sends into that * state, so the best we can do is issue SSL_shutdown but not check the * errors / returns. At least this enables the receiver to be sure of the * integrity of the send in terms of unwanted truncation. */ ret = SSL_shutdown(p_ssl); maybe_log_shutdown_state(p_sess); if (ret == 0) { ret = SSL_shutdown(p_ssl); maybe_log_shutdown_state(p_sess); if (ret != 1) { if (tunable_strict_ssl_write_shutdown) { success = 0; } maybe_log_shutdown_state(p_sess); maybe_log_ssl_error_state(p_sess, ret); } } else if (ret < 0) { if (tunable_strict_ssl_write_shutdown) { success = 0; } maybe_log_ssl_error_state(p_sess, ret); } SSL_free(p_ssl); p_sess->p_data_ssl = NULL; } return success; } int ssl_accept(struct vsf_session* p_sess, int fd) { /* SECURITY: data SSL connections don't have any auth on them as part of the * protocol. If a client sends an unfortunately optional client cert then * we can check for a match between the control and data connections. */ SSL* p_ssl; int reused; if (p_sess->p_data_ssl != NULL) { die("p_data_ssl should be NULL."); } p_ssl = get_ssl(p_sess, fd); if (p_ssl == NULL) { return 0; } p_sess->p_data_ssl = p_ssl; setup_bio_callbacks(p_ssl); reused = SSL_session_reused(p_ssl); if (tunable_require_ssl_reuse && !reused) { str_alloc_text(&debug_str, "No SSL session reuse on data channel."); vsf_log_line(p_sess, kVSFLogEntryDebug, &debug_str); ssl_data_close(p_sess); return 0; } if (str_getlen(&p_sess->control_cert_digest) > 0) { static struct mystr data_cert_digest; if (!ssl_cert_digest(p_ssl, p_sess, &data_cert_digest)) { str_alloc_text(&debug_str, "Missing cert on data channel."); vsf_log_line(p_sess, kVSFLogEntryDebug, &debug_str); ssl_data_close(p_sess); return 0; } if (str_strcmp(&p_sess->control_cert_digest, &data_cert_digest)) { str_alloc_text(&debug_str, "DIFFERENT cert on data channel."); vsf_log_line(p_sess, kVSFLogEntryDebug, &debug_str); ssl_data_close(p_sess); return 0; } if (tunable_debug_ssl) { str_alloc_text(&debug_str, "Matching cert on data channel."); vsf_log_line(p_sess, kVSFLogEntryDebug, &debug_str); } } return 1; } void ssl_comm_channel_init(struct vsf_session* p_sess) { const struct vsf_sysutil_socketpair_retval retval = vsf_sysutil_unix_stream_socketpair(); if (p_sess->ssl_consumer_fd != -1) { bug("ssl_consumer_fd active"); } if (p_sess->ssl_slave_fd != -1) { bug("ssl_slave_fd active"); } p_sess->ssl_consumer_fd = retval.socket_one; p_sess->ssl_slave_fd = retval.socket_two; } void ssl_comm_channel_set_consumer_context(struct vsf_session* p_sess) { if (p_sess->ssl_slave_fd == -1) { bug("ssl_slave_fd already closed"); } vsf_sysutil_close(p_sess->ssl_slave_fd); p_sess->ssl_slave_fd = -1; } void ssl_comm_channel_set_producer_context(struct vsf_session* p_sess) { if (p_sess->ssl_consumer_fd == -1) { bug("ssl_consumer_fd already closed"); } vsf_sysutil_close(p_sess->ssl_consumer_fd); p_sess->ssl_consumer_fd = -1; } static SSL* get_ssl(struct vsf_session* p_sess, int fd) { SSL* p_ssl = SSL_new(p_sess->p_ssl_ctx); if (p_ssl == NULL) { if (tunable_debug_ssl) { str_alloc_text(&debug_str, "SSL_new failed"); vsf_log_line(p_sess, kVSFLogEntryDebug, &debug_str); } return NULL; } if (!SSL_set_fd(p_ssl, fd)) { if (tunable_debug_ssl) { str_alloc_text(&debug_str, "SSL_set_fd failed"); vsf_log_line(p_sess, kVSFLogEntryDebug, &debug_str); } SSL_free(p_ssl); return NULL; } if (SSL_accept(p_ssl) != 1) { const char* p_err = get_ssl_error(); if (tunable_debug_ssl) { str_alloc_text(&debug_str, "SSL_accept failed: "); str_append_text(&debug_str, p_err); vsf_log_line(p_sess, kVSFLogEntryDebug, &debug_str); } /* The RFC is quite clear that we can just close the control channel * here. */ die(p_err); } if (tunable_debug_ssl) { const char* p_ssl_version = SSL_get_cipher_version(p_ssl); const SSL_CIPHER* p_ssl_cipher = SSL_get_current_cipher(p_ssl); const char* p_cipher_name = SSL_CIPHER_get_name(p_ssl_cipher); X509* p_ssl_cert = SSL_get_peer_certificate(p_ssl); int reused = SSL_session_reused(p_ssl); str_alloc_text(&debug_str, "SSL version: "); str_append_text(&debug_str, p_ssl_version); str_append_text(&debug_str, ", SSL cipher: "); str_append_text(&debug_str, p_cipher_name); if (reused) { str_append_text(&debug_str, ", reused"); } else { str_append_text(&debug_str, ", not reused"); } if (p_ssl_cert != NULL) { str_append_text(&debug_str, ", CERT PRESENTED"); X509_free(p_ssl_cert); } else { str_append_text(&debug_str, ", no cert"); } vsf_log_line(p_sess, kVSFLogEntryDebug, &debug_str); } return p_ssl; } static int ssl_session_init(struct vsf_session* p_sess) { SSL* p_ssl = get_ssl(p_sess, VSFTP_COMMAND_FD); if (p_ssl == NULL) { return 0; } p_sess->p_control_ssl = p_ssl; (void) ssl_cert_digest(p_ssl, p_sess, &p_sess->control_cert_digest); setup_bio_callbacks(p_ssl); return 1; } static int ssl_cert_digest(SSL* p_ssl, struct vsf_session* p_sess, struct mystr* p_str) { X509* p_cert = SSL_get_peer_certificate(p_ssl); unsigned int num_bytes = 0; if (p_cert == NULL) { return 0; } str_reserve(p_str, EVP_MAX_MD_SIZE); str_empty(p_str); str_rpad(p_str, EVP_MAX_MD_SIZE); if (!X509_digest(p_cert, EVP_sha256(), (unsigned char*) str_getbuf(p_str), &num_bytes)) { die("X509_digest failed"); } X509_free(p_cert); if (tunable_debug_ssl) { unsigned int i; str_alloc_text(&debug_str, "Cert digest:"); for (i = 0; i < num_bytes; ++i) { str_append_char(&debug_str, ' '); str_append_ulong( &debug_str, (unsigned long) (unsigned char) str_get_char_at(p_str, i)); } vsf_log_line(p_sess, kVSFLogEntryDebug, &debug_str); } str_trunc(p_str, num_bytes); return 1; } static char* get_ssl_error() { SSL_load_error_strings(); return ERR_error_string(ERR_get_error(), NULL); } static void setup_bio_callbacks(SSL* p_ssl) { BIO* p_bio = SSL_get_rbio(p_ssl); BIO_set_callback(p_bio, bio_callback); p_bio = SSL_get_wbio(p_ssl); BIO_set_callback(p_bio, bio_callback); } static long bio_callback( BIO* p_bio, int oper, const char* p_arg, int argi, long argl, long ret) { int retval = 0; int fd = 0; (void) p_arg; (void) argi; (void) argl; if (oper == (BIO_CB_READ | BIO_CB_RETURN) || oper == (BIO_CB_WRITE | BIO_CB_RETURN)) { retval = (int) ret; fd = BIO_get_fd(p_bio, NULL); } vsf_sysutil_check_pending_actions(kVSFSysUtilIO, retval, fd); return ret; } static int ssl_verify_callback(int verify_ok, X509_STORE_CTX* p_ctx) { (void) p_ctx; if (tunable_validate_cert) { return verify_ok; } return 1; } void ssl_add_entropy(struct vsf_session* p_sess) { /* Although each child does seem to have its different pool of entropy, I * don't trust the interaction of OpenSSL's opaque RAND API and fork(). So * throw a bit more in (only works on systems with /dev/urandom for now). */ int ret = RAND_load_file("/dev/urandom", 16); if (ret != 16) { str_alloc_text(&debug_str, "Couldn't add extra OpenSSL entropy: "); str_append_ulong(&debug_str, (unsigned long) ret); vsf_log_line(p_sess, kVSFLogEntryDebug, &debug_str); } } #else /* VSF_BUILD_SSL */ void ssl_init(struct vsf_session* p_sess) { (void) p_sess; die("SSL: ssl_enable is set but SSL support not compiled in"); } void ssl_control_handshake(struct vsf_session* p_sess) { (void) p_sess; } void handle_auth(struct vsf_session* p_sess) { (void) p_sess; } void handle_pbsz(struct vsf_session* p_sess) { (void) p_sess; } void handle_prot(struct vsf_session* p_sess) { (void) p_sess; } int ssl_read(struct vsf_session* p_sess, void* p_ssl, char* p_buf, unsigned int len) { (void) p_sess; (void) p_ssl; (void) p_buf; (void) len; return -1; } int ssl_peek(struct vsf_session* p_sess, void* p_ssl, char* p_buf, unsigned int len) { (void) p_sess; (void) p_ssl; (void) p_buf; (void) len; return -1; } int ssl_write(void* p_ssl, const char* p_buf, unsigned int len) { (void) p_ssl; (void) p_buf; (void) len; return -1; } int ssl_write_str(void* p_ssl, const struct mystr* p_str) { (void) p_ssl; (void) p_str; return -1; } int ssl_accept(struct vsf_session* p_sess, int fd) { (void) p_sess; (void) fd; return -1; } int ssl_data_close(struct vsf_session* p_sess) { (void) p_sess; return 1; } void ssl_comm_channel_init(struct vsf_session* p_sess) { (void) p_sess; } void ssl_comm_channel_set_consumer_context(struct vsf_session* p_sess) { (void) p_sess; } void ssl_comm_channel_set_producer_context(struct vsf_session* p_sess) { (void) p_sess; } void ssl_add_entropy(struct vsf_session* p_sess) { (void) p_sess; } int ssl_read_into_str(struct vsf_session* p_sess, void* p_ssl, struct mystr* p_str) { (void) p_sess; (void) p_ssl; (void) p_str; return -1; } #endif /* VSF_BUILD_SSL */ vsftpd-3.0.3/ftpdataio.h0000644000175000017500000000660411043521144014152 0ustar chrischris#ifndef VSF_FTPDATAIO_H #define VSF_FTPDATAIO_H #include "filesize.h" struct mystr; struct vsf_sysutil_sockaddr; struct vsf_sysutil_dir; struct vsf_session; /* vsf_ftpdataio_dispose_transfer_fd() * PURPOSE * Close down the remote data transfer file descriptor. If unsent data reamins * on the connection, this method blocks until it is transferred (or the data * timeout goes off, or the connection is severed). * PARAMETERS * p_sess - the current FTP session object * RETURNS * 1 on success, 0 otherwise. * */ int vsf_ftpdataio_dispose_transfer_fd(struct vsf_session* p_sess); /* vsf_ftpdataio_get_pasv_fd() * PURPOSE * Return a connection data file descriptor obtained by the PASV connection * method. This includes accept()'ing a connection from the remote. * PARAMETERS * p_sess - the current FTP session object * RETURNS * The file descriptor upon success, or -1 upon error. */ int vsf_ftpdataio_get_pasv_fd(struct vsf_session* p_sess); /* vsf_ftpdataio_get_pasv_fd() * PURPOSE * Return a connection data file descriptor obtained by the PORT connection * method. This includes connect()'ing to the remote. * PARAMETERS * p_sess - the current FTP session object * RETURNS * The file descriptor upon success, or -1 upon error. */ int vsf_ftpdataio_get_port_fd(struct vsf_session* p_sess); /* vsf_ftpdataio_post_mark_connect() * PURPOSE * Perform any post-150-status-mark setup on the data connection. For example, * the negotiation of SSL. * PARAMETERS * p_sess - the current FTP session object * RETURNS * 1 on success, 0 otherwise. */ int vsf_ftpdataio_post_mark_connect(struct vsf_session* p_sess); /* vsf_ftpdataio_transfer_file() * PURPOSE * Send data between the network and a local file. Send and receive are * supported, as well as ASCII mangling. * PARAMETERS * remote_fd - the file descriptor of the remote data connection * file_fd - the file descriptor of the local file * is_recv - 0 for sending to the remote, otherwise receive * is_ascii - non zero for ASCII mangling * RETURNS * A structure, containing * retval - 0 for success, failure otherwise * (-1 = local problem -2 = remote problem) * transferred - number of bytes transferred */ struct vsf_transfer_ret { int retval; filesize_t transferred; }; struct vsf_transfer_ret vsf_ftpdataio_transfer_file( struct vsf_session* p_sess, int remote_fd, int file_fd, int is_recv, int is_ascii); /* vsf_ftpdataio_transfer_dir() * PURPOSE * Send an ASCII directory lising of the requested directory to the remote * client. * PARAMETERS * p_sess - the current session object * is_control - whether to send on the control connection or data connection * p_dir - the local directory object * p_base_dir_str - the directory we opened relative to the current one * p_option_str - the options list provided to "ls" * p_filter_str - the filter string provided to "ls" * is_verbose - set to 0 if NLST used, 1 if LIST used */ int vsf_ftpdataio_transfer_dir(struct vsf_session* p_sess, int is_control, struct vsf_sysutil_dir* p_dir, const struct mystr* p_base_dir_str, const struct mystr* p_option_str, const struct mystr* p_filter_str, int is_verbose); #endif /* VSF_FTPDATAIO_H */ vsftpd-3.0.3/tcpwrap.c0000644000175000017500000000164111122254715013653 0ustar chrischris/* * Part of Very Secure FTPd * Licence: GPL v2 * Author: Chris Evans * tcpwrap.c * * Routines to encapsulate the usage of tcp_wrappers. */ #include "tcpwrap.h" #include "builddefs.h" #include "utility.h" #include "sysutil.h" #ifdef VSF_BUILD_TCPWRAPPERS #include #endif #ifdef VSF_BUILD_TCPWRAPPERS #include int deny_severity = LOG_WARNING; int allow_severity = LOG_INFO; int vsf_tcp_wrapper_ok(int remote_fd) { struct request_info req; vsf_sysutil_openlog(0); request_init(&req, RQ_DAEMON, "vsftpd", RQ_FILE, remote_fd, 0); fromhost(&req); if (!hosts_access(&req)) { vsf_sysutil_closelog(); return 0; } vsf_sysutil_closelog(); return 1; } #else /* VSF_BUILD_TCPWRAPPERS */ int vsf_tcp_wrapper_ok(int remote_fd) { (void) remote_fd; die("tcp_wrappers is set to YES but no tcp wrapper support compiled in"); return 0; } #endif /* VSF_BUILD_TCPWRAPPERS */ vsftpd-3.0.3/README.security0000644000175000017500000000016010750743701014554 0ustar chrischrisFor documentation about the security of vsftpd, please consult the files located within the SECURITY directory. vsftpd-3.0.3/BUGS0000644000175000017500000000146611734501671012524 0ustar chrischrisBUGS ==== This file, surprisingly enough, contains a list of known outstanding bugs in the program. Bugs that get documented here are typically not particularly serious, and may never get fixed. Serious bugs will get fixed immediately. - RFC compliance: if we get no PORT or PASV, looks like we're supposed to assume a PORT to the same IP as control connection, and port 20. - RFC compliance: shouldn't include directories in NLST. Note that wu-ftpd complies here, almost all other FTPd's don't - ls should list nothing, but it lists the directory name itself - In ASCII mode, the SIZE command needs to take into account the size of the file _after_ ASCII linefeed mangling? - If someone has one of the timeouts (command, data) setup, but not the other, then timeout will behave whackily. vsftpd-3.0.3/ftpcmdio.c0000644000175000017500000001527311736503401014006 0ustar chrischris/* * Part of Very Secure FTPd * Licence: GPL v2 * Author: Chris Evans * ftpcmdio.c * * Routines applicable to reading and writing the FTP command stream. */ #include "ftpcmdio.h" #include "ftpcodes.h" #include "str.h" #include "netstr.h" #include "sysutil.h" #include "tunables.h" #include "defs.h" #include "secbuf.h" #include "utility.h" #include "logging.h" #include "session.h" #include "readwrite.h" /* Internal functions */ static int control_getline(struct mystr* p_str, struct vsf_session* p_sess); static void ftp_write_text_common(struct vsf_session* p_sess, int status, const char* p_text, char sep); static void ftp_write_str_common(struct vsf_session* p_sess, int status, char sep, const struct mystr* p_str); static void handle_alarm_timeout(void* p_private); void vsf_cmdio_sock_setup(void) { vsf_sysutil_activate_keepalive(VSFTP_COMMAND_FD); vsf_sysutil_set_nodelay(VSFTP_COMMAND_FD); vsf_sysutil_activate_oobinline(VSFTP_COMMAND_FD); } static void handle_alarm_timeout(void* p_private) { struct vsf_session* p_sess = (struct vsf_session*) p_private; p_sess->idle_timeout = 1; vsf_sysutil_activate_noblock(VSFTP_COMMAND_FD); vsf_sysutil_shutdown_read_failok(VSFTP_COMMAND_FD); } void vsf_cmdio_write(struct vsf_session* p_sess, int status, const char* p_text) { ftp_write_text_common(p_sess, status, p_text, ' '); } void vsf_cmdio_write_hyphen(struct vsf_session* p_sess, int status, const char* p_text) { ftp_write_text_common(p_sess, status, p_text, '-'); } void vsf_cmdio_write_raw(struct vsf_session* p_sess, const char* p_text) { static struct mystr s_the_str; int retval; str_alloc_text(&s_the_str, p_text); if (tunable_log_ftp_protocol) { vsf_log_line(p_sess, kVSFLogEntryFTPOutput, &s_the_str); } retval = ftp_write_str(p_sess, &s_the_str, kVSFRWControl); if (retval != 0) { die("ftp_write_str"); } } void vsf_cmdio_write_exit(struct vsf_session* p_sess, int status, const char* p_text, int exit_val) { /* Unblock any readers on the dying control channel. This is needed for SSL * connections, where the SSL control channel slave is in a separate * process. */ vsf_sysutil_activate_noblock(VSFTP_COMMAND_FD); vsf_sysutil_shutdown_read_failok(VSFTP_COMMAND_FD); vsf_cmdio_write(p_sess, status, p_text); vsf_sysutil_shutdown_failok(VSFTP_COMMAND_FD); vsf_sysutil_exit(exit_val); } static void ftp_write_text_common(struct vsf_session* p_sess, int status, const char* p_text, char sep) { /* XXX - could optimize */ static struct mystr s_the_str; str_alloc_text(&s_the_str, p_text); ftp_write_str_common(p_sess, status, sep, &s_the_str); } void vsf_cmdio_write_str_hyphen(struct vsf_session* p_sess, int status, const struct mystr* p_str) { ftp_write_str_common(p_sess, status, '-', p_str); } void vsf_cmdio_write_str(struct vsf_session* p_sess, int status, const struct mystr* p_str) { ftp_write_str_common(p_sess, status, ' ', p_str); } static void ftp_write_str_common(struct vsf_session* p_sess, int status, char sep, const struct mystr* p_str) { static struct mystr s_write_buf_str; static struct mystr s_text_mangle_str; int retval; if (tunable_log_ftp_protocol) { str_alloc_ulong(&s_write_buf_str, (unsigned long) status); str_append_char(&s_write_buf_str, sep); str_append_str(&s_write_buf_str, p_str); vsf_log_line(p_sess, kVSFLogEntryFTPOutput, &s_write_buf_str); } str_copy(&s_text_mangle_str, p_str); /* Process the output response according to the specifications.. */ /* Escape telnet characters properly */ str_replace_text(&s_text_mangle_str, "\377", "\377\377"); /* Change \n for \0 in response */ str_replace_char(&s_text_mangle_str, '\n', '\0'); /* Build string to squirt down network */ str_alloc_ulong(&s_write_buf_str, (unsigned long) status); str_append_char(&s_write_buf_str, sep); str_append_str(&s_write_buf_str, &s_text_mangle_str); str_append_text(&s_write_buf_str, "\r\n"); retval = ftp_write_str(p_sess, &s_write_buf_str, kVSFRWControl); if (retval != 0) { die("ftp_write"); } } void vsf_cmdio_set_alarm(struct vsf_session* p_sess) { if (tunable_idle_session_timeout > 0) { vsf_sysutil_install_sighandler(kVSFSysUtilSigALRM, handle_alarm_timeout, p_sess, 1); vsf_sysutil_set_alarm(tunable_idle_session_timeout); } } void vsf_cmdio_get_cmd_and_arg(struct vsf_session* p_sess, struct mystr* p_cmd_str, struct mystr* p_arg_str, int set_alarm) { int ret; /* Prepare an alarm to timeout the session.. */ if (set_alarm) { vsf_cmdio_set_alarm(p_sess); } /* Blocks */ ret = control_getline(p_cmd_str, p_sess); if (p_sess->idle_timeout) { vsf_cmdio_write_exit(p_sess, FTP_IDLE_TIMEOUT, "Timeout.", 1); } if (ret == 0) { /* Remote end hung up without a polite QUIT. The shutdown is to make * sure buggy clients don't ever see an OOPS message. */ vsf_sysutil_shutdown_failok(VSFTP_COMMAND_FD); vsf_sysutil_exit(1); } /* View a single space as a command of " ", which although a useless command, * permits the caller to distinguish input of "" from " ". */ if (str_getlen(p_cmd_str) == 1 && str_get_char_at(p_cmd_str, 0) == ' ') { str_empty(p_arg_str); } else { str_split_char(p_cmd_str, p_arg_str, ' '); } str_upper(p_cmd_str); if (tunable_log_ftp_protocol) { static struct mystr s_log_str; if (str_equal_text(p_cmd_str, "PASS")) { str_alloc_text(&s_log_str, "PASS "); } else { str_copy(&s_log_str, p_cmd_str); if (!str_isempty(p_arg_str)) { str_append_char(&s_log_str, ' '); str_append_str(&s_log_str, p_arg_str); } } vsf_log_line(p_sess, kVSFLogEntryFTPInput, &s_log_str); } } static int control_getline(struct mystr* p_str, struct vsf_session* p_sess) { int ret; if (p_sess->p_control_line_buf == 0) { vsf_secbuf_alloc(&p_sess->p_control_line_buf, VSFTP_MAX_COMMAND_LINE); } ret = ftp_getline(p_sess, p_str, p_sess->p_control_line_buf); if (ret == 0) { return ret; } else if (ret < 0) { vsf_cmdio_write_exit(p_sess, FTP_BADCMD, "Input line too long.", 1); } /* As mandated by the FTP specifications.. */ str_replace_char(p_str, '\0', '\n'); /* If the last character is a \r, strip it */ { unsigned int len = str_getlen(p_str); while (len > 0 && str_get_char_at(p_str, len - 1) == '\r') { str_trunc(p_str, len - 1); --len; } } return 1; } vsftpd-3.0.3/seccompsandbox.h0000644000175000017500000000056411734666326015231 0ustar chrischris#ifndef VSF_SECCOMPSANDBOX_H #define VSF_SECCOMPSANDBOX_H struct vsf_session; void seccomp_sandbox_init(); void seccomp_sandbox_setup_prelogin(const struct vsf_session* p_sess); void seccomp_sandbox_setup_postlogin(const struct vsf_session* p_sess); void seccomp_sandbox_setup_postlogin_broker(); void seccomp_sandbox_lockdown(); #endif /* VSF_SECCOMPSANDBOX_H */ vsftpd-3.0.3/secutil.h0000644000175000017500000000367411734500212013653 0ustar chrischris#ifndef VSF_SECUTIL_H #define VSF_SECUTIL_H struct mystr; /* vsf_secutil_change_credentials() * PURPOSE * This function securely switches process credentials to the user specified. * There are options to enter a chroot() jail, and supplementary groups may * or may not be activated. * PARAMETERS * p_user_str - the name of the user to become * p_dir_str - the directory to chdir() and possibly chroot() to. * (if NULL, the user's home directory is used) * p_ext_dir_str - the directory to chdir() and possibly chroot() to, * applied in addition to the directory calculated by * p_user_str and p_dir_str. * caps - bitmap of capabilities to adopt. NOTE, if the underlying * OS does not support capabilities as a non-root user, and * the capability bitset is non-empty, then root privileges * will have to be retained. * options - see bitmask definitions below */ /* chroot() the user into the new directory */ #define VSF_SECUTIL_OPTION_CHROOT 1 /* Activate any supplementary groups the user may have */ #define VSF_SECUTIL_OPTION_USE_GROUPS 2 /* Do the chdir() as the effective userid of the target user */ #define VSF_SECUTIL_OPTION_CHANGE_EUID 4 /* Use RLIMIT_NOFILE to prevent the opening of new fds */ #define VSF_SECUTIL_OPTION_NO_FDS 8 /* Use RLIMIT_NPROC to prevent the launching of new processes */ #define VSF_SECUTIL_OPTION_NO_PROCS 16 /* Permit a writeable chroot() root */ #define VSF_SECUTIL_OPTION_ALLOW_WRITEABLE_ROOT 32 void vsf_secutil_change_credentials(const struct mystr* p_user_str, const struct mystr* p_dir_str, const struct mystr* p_ext_dir_str, unsigned int caps, unsigned int options); #endif /* VSF_SECUTIL_H */ vsftpd-3.0.3/COPYRIGHT0000644000175000017500000000016411224724462013325 0ustar chrischrisEverything within this tar archive is Copyright (c) Chris Evans, except where otherwise noted in individual files. vsftpd-3.0.3/SIZE0000644000175000017500000000061010750743701012523 0ustar chrischrisI'm not sure what you expected to find in this file :-) Anyway, this is to explain that vsftpd is not as much code as you might expect from running a command like "wc -l *.c". Why? Simply because I use a very verbose style of coding in vsftpd, which consumes a lot of lines. Verbose code is very important in a secure program. How can you verify a program's security if it is not readable? vsftpd-3.0.3/sysutil.c0000644000175000017500000017075612025275272013731 0ustar chrischris/* * Part of Very Secure FTPd * Licence: GPL v2 * Author: Chris Evans * * sysutil.c * * Routines to make the libc/syscall API more pleasant to use. Long term, * more libc/syscalls will go in here to reduce the number of .c files with * dependencies on libc or syscalls. */ #define PRIVATE_HANDS_OFF_syscall_retval syscall_retval #define PRIVATE_HANDS_OFF_exit_status exit_status #include "sysutil.h" #include "utility.h" #include "tunables.h" #include "sysdeputil.h" /* Activate 64-bit file support on Linux/32bit plus others */ #define _FILE_OFFSET_BITS 64 #define _LARGEFILE_SOURCE 1 #define _LARGEFILE64_SOURCE 1 #define _LARGE_FILES 1 /* For Linux, this adds nothing :-) */ #include "port/porting_junk.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Must be before netinet/ip.h. Found on FreeBSD, Solaris */ #include #include #include #include #include #include #include #include /* Private variables to this file */ /* Current umask() */ static unsigned int s_current_umask; /* Cached time */ static struct timeval s_current_time; /* Current pid */ static int s_current_pid = -1; /* Exit function */ static exitfunc_t s_exit_func; /* Difference in timezone from GMT in seconds */ static long s_timezone; /* Our internal signal handling implementation details */ static struct vsf_sysutil_sig_details { vsf_sighandle_t sync_sig_handler; void* p_private; volatile sig_atomic_t pending; int running; int use_alarm; } s_sig_details[NSIG]; static vsf_context_io_t s_io_handler; static void* s_p_io_handler_private; static int s_io_handler_running; struct vsf_sysutil_sockaddr { union { struct sockaddr u_sockaddr; struct sockaddr_in u_sockaddr_in; struct sockaddr_in6 u_sockaddr_in6; } u; }; /* File locals */ static void vsf_sysutil_common_sighandler(int signum); static void vsf_sysutil_alrm_sighandler(int signum); static int vsf_sysutil_translate_sig(const enum EVSFSysUtilSignal sig); static void vsf_sysutil_set_sighandler(int sig, void (*p_handlefunc)(int)); static int vsf_sysutil_translate_memprot( const enum EVSFSysUtilMapPermission perm); static int vsf_sysutil_translate_openmode( const enum EVSFSysUtilOpenMode mode); static void vsf_sysutil_alloc_statbuf(struct vsf_sysutil_statbuf** p_ptr); void vsf_sysutil_sockaddr_alloc(struct vsf_sysutil_sockaddr** p_sockptr); static int lock_internal(int fd, int lock_type); static void vsf_sysutil_alrm_sighandler(int signum) { (void) signum; alarm(1); } static void vsf_sysutil_common_sighandler(int signum) { if (signum < 0 || signum >= NSIG) { /* "cannot happen" */ return; } if (s_sig_details[signum].sync_sig_handler) { s_sig_details[signum].pending = 1; /* Since this synchronous signal framework has a small race (signal coming * in just before we start a blocking call), there's the option to fire an * alarm repeatedly until the signal is handled. */ if (s_sig_details[signum].use_alarm) { alarm(1); } } } /* Notes. This signal check is evaluated after potentially blocking system * calls, i.e. at highly defined points in the code. Since we only interrupt * at these definite locations, the signal handlers can be non-trivial * without us having to worry about re-entrancy. * * We guarantee that a handler for a given signal is not re-entrant. This * is taken care of by the "running" flag. * * This call itself can only be re-entered once we dereference the actual * hander function pointer, so we are safe with respect to races modifying * the "running" flag. */ void vsf_sysutil_check_pending_actions( const enum EVSFSysUtilInterruptContext context, int retval, int fd) { unsigned int i; /* Check the i/o handler before the signal handlers */ if (s_io_handler && !s_io_handler_running && context == kVSFSysUtilIO) { s_io_handler_running = 1; (*s_io_handler)(retval, fd, s_p_io_handler_private); s_io_handler_running = 0; } for (i=0; i < NSIG; ++i) { if (s_sig_details[i].pending && !s_sig_details[i].running) { s_sig_details[i].running = 1; if (s_sig_details[i].use_alarm) { alarm(0); } if (s_sig_details[i].sync_sig_handler) { s_sig_details[i].pending = 0; (*(s_sig_details[i].sync_sig_handler))(s_sig_details[i].p_private); } s_sig_details[i].running = 0; } } } static int vsf_sysutil_translate_sig(const enum EVSFSysUtilSignal sig) { int realsig = 0; switch (sig) { case kVSFSysUtilSigALRM: realsig = SIGALRM; break; case kVSFSysUtilSigTERM: realsig = SIGTERM; break; case kVSFSysUtilSigCHLD: realsig = SIGCHLD; break; case kVSFSysUtilSigPIPE: realsig = SIGPIPE; break; case kVSFSysUtilSigURG: realsig = SIGURG; break; case kVSFSysUtilSigHUP: realsig = SIGHUP; break; default: bug("unknown signal in vsf_sysutil_translate_sig"); break; } if (realsig < 0 || realsig >= NSIG) { bug("signal out of range in vsf_sysutil_translate_sig"); } return realsig; } void vsf_sysutil_install_sighandler(const enum EVSFSysUtilSignal sig, vsf_sighandle_t handler, void* p_private, int use_alarm) { int realsig = vsf_sysutil_translate_sig(sig); s_sig_details[realsig].p_private = p_private; s_sig_details[realsig].sync_sig_handler = handler; s_sig_details[realsig].use_alarm = use_alarm; vsf_sysutil_set_sighandler(realsig, vsf_sysutil_common_sighandler); if (use_alarm && realsig != SIGALRM) { vsf_sysutil_set_sighandler(SIGALRM, vsf_sysutil_alrm_sighandler); } } void vsf_sysutil_default_sig(const enum EVSFSysUtilSignal sig) { int realsig = vsf_sysutil_translate_sig(sig); vsf_sysutil_set_sighandler(realsig, SIG_DFL); s_sig_details[realsig].p_private = NULL; s_sig_details[realsig].sync_sig_handler = NULL; } void vsf_sysutil_install_null_sighandler(const enum EVSFSysUtilSignal sig) { int realsig = vsf_sysutil_translate_sig(sig); s_sig_details[realsig].p_private = NULL; s_sig_details[realsig].sync_sig_handler = NULL; vsf_sysutil_set_sighandler(realsig, vsf_sysutil_common_sighandler); } void vsf_sysutil_install_async_sighandler(const enum EVSFSysUtilSignal sig, vsf_async_sighandle_t handler) { int realsig = vsf_sysutil_translate_sig(sig); s_sig_details[realsig].p_private = NULL; s_sig_details[realsig].sync_sig_handler = NULL; vsf_sysutil_block_sig(sig); vsf_sysutil_set_sighandler(realsig, handler); } static void vsf_sysutil_set_sighandler(int sig, void (*p_handlefunc)(int)) { int retval; struct sigaction sigact; vsf_sysutil_memclr(&sigact, sizeof(sigact)); sigact.sa_handler = p_handlefunc; retval = sigfillset(&sigact.sa_mask); if (retval != 0) { die("sigfillset"); } retval = sigaction(sig, &sigact, NULL); if (retval != 0) { die("sigaction"); } } void vsf_sysutil_block_sig(const enum EVSFSysUtilSignal sig) { sigset_t sset; int retval; int realsig = vsf_sysutil_translate_sig(sig); retval = sigemptyset(&sset); if (retval != 0) { die("sigemptyset"); } retval = sigaddset(&sset, realsig); if (retval != 0) { die("sigaddset"); } retval = sigprocmask(SIG_BLOCK, &sset, NULL); if (retval != 0) { die("sigprocmask"); } } void vsf_sysutil_unblock_sig(const enum EVSFSysUtilSignal sig) { sigset_t sset; int retval; int realsig = vsf_sysutil_translate_sig(sig); retval = sigemptyset(&sset); if (retval != 0) { die("sigemptyset"); } retval = sigaddset(&sset, realsig); if (retval != 0) { die("sigaddset"); } retval = sigprocmask(SIG_UNBLOCK, &sset, NULL); if (retval != 0) { die("sigprocmask"); } } void vsf_sysutil_install_io_handler(vsf_context_io_t handler, void* p_private) { if (s_io_handler != NULL) { bug("double register of i/o handler"); } s_io_handler = handler; s_p_io_handler_private = p_private; } void vsf_sysutil_uninstall_io_handler(void) { if (s_io_handler == NULL) { bug("no i/o handler to unregister!"); } s_io_handler = NULL; s_p_io_handler_private = NULL; } void vsf_sysutil_set_alarm(const unsigned int trigger_seconds) { (void) alarm(trigger_seconds); } void vsf_sysutil_clear_alarm(void) { vsf_sysutil_set_alarm(0); } int vsf_sysutil_read(const int fd, void* p_buf, const unsigned int size) { while (1) { int retval = read(fd, p_buf, size); int saved_errno = errno; vsf_sysutil_check_pending_actions(kVSFSysUtilIO, retval, fd); if (retval < 0 && saved_errno == EINTR) { continue; } return retval; } } int vsf_sysutil_write(const int fd, const void* p_buf, const unsigned int size) { while (1) { int retval = write(fd, p_buf, size); int saved_errno = errno; vsf_sysutil_check_pending_actions(kVSFSysUtilIO, retval, fd); if (retval < 0 && saved_errno == EINTR) { continue; } return retval; } } int vsf_sysutil_read_loop(const int fd, void* p_buf, unsigned int size) { int retval; int num_read = 0; if (size > INT_MAX) { die("size too big in vsf_sysutil_read_loop"); } while (1) { retval = vsf_sysutil_read(fd, (char*)p_buf + num_read, size); if (retval < 0) { return retval; } else if (retval == 0) { /* Read all we're going to read.. */ return num_read; } if ((unsigned int) retval > size) { die("retval too big in vsf_sysutil_read_loop"); } num_read += retval; size -= (unsigned int) retval; if (size == 0) { /* Hit the read target, cool. */ return num_read; } } } int vsf_sysutil_write_loop(const int fd, const void* p_buf, unsigned int size) { int retval; int num_written = 0; if (size > INT_MAX) { die("size too big in vsf_sysutil_write_loop"); } while (1) { retval = vsf_sysutil_write(fd, (const char*)p_buf + num_written, size); if (retval < 0) { /* Error */ return retval; } else if (retval == 0) { /* Written all we're going to write.. */ return num_written; } if ((unsigned int) retval > size) { die("retval too big in vsf_sysutil_write_loop"); } num_written += retval; size -= (unsigned int) retval; if (size == 0) { /* Hit the write target, cool. */ return num_written; } } } filesize_t vsf_sysutil_get_file_offset(const int file_fd) { filesize_t retval = lseek(file_fd, 0, SEEK_CUR); if (retval < 0) { die("lseek"); } return retval; } void vsf_sysutil_lseek_to(const int fd, filesize_t seek_pos) { filesize_t retval; if (seek_pos < 0) { die("negative seek_pos in vsf_sysutil_lseek_to"); } retval = lseek(fd, seek_pos, SEEK_SET); if (retval < 0) { die("lseek"); } } void vsf_sysutil_lseek_end(const int fd) { filesize_t retval; retval = lseek(fd, 0, SEEK_END); if (retval < 0) { die("lseek"); } } void* vsf_sysutil_malloc(unsigned int size) { void* p_ret; /* Paranoia - what if we got an integer overflow/underflow? */ if (size == 0 || size > INT_MAX) { bug("zero or big size in vsf_sysutil_malloc"); } p_ret = malloc(size); if (p_ret == NULL) { die("malloc"); } return p_ret; } void* vsf_sysutil_realloc(void* p_ptr, unsigned int size) { void* p_ret; if (size == 0 || size > INT_MAX) { bug("zero or big size in vsf_sysutil_realloc"); } p_ret = realloc(p_ptr, size); if (p_ret == NULL) { die("realloc"); } return p_ret; } void vsf_sysutil_free(void* p_ptr) { if (p_ptr == NULL) { bug("vsf_sysutil_free got a null pointer"); } free(p_ptr); } unsigned int vsf_sysutil_getpid(void) { if (s_current_pid == -1) { s_current_pid = vsf_sysutil_getpid_nocache(); } return (unsigned int) s_current_pid; } int vsf_sysutil_fork(void) { int retval = vsf_sysutil_fork_failok(); if (retval < 0) { die("fork"); } return retval; } int vsf_sysutil_fork_failok(void) { int retval; retval = fork(); if (retval == 0) { vsf_sysutil_post_fork(); } return retval; } void vsf_sysutil_set_exit_func(exitfunc_t exitfunc) { s_exit_func = exitfunc; } void vsf_sysutil_exit(int exit_code) { if (s_exit_func) { exitfunc_t curr_func = s_exit_func; /* Prevent recursion */ s_exit_func = 0; (*curr_func)(); } _exit(exit_code); } struct vsf_sysutil_wait_retval vsf_sysutil_wait(void) { struct vsf_sysutil_wait_retval retval; vsf_sysutil_memclr(&retval, sizeof(retval)); while (1) { int sys_ret = wait(&retval.exit_status); if (sys_ret < 0 && errno == EINTR) { vsf_sysutil_check_pending_actions(kVSFSysUtilUnknown, 0, 0); continue; } retval.syscall_retval = sys_ret; return retval; } } int vsf_sysutil_wait_reap_one(void) { int retval = waitpid(-1, NULL, WNOHANG); if (retval == 0 || (retval < 0 && errno == ECHILD)) { /* No more children */ return 0; } if (retval < 0) { die("waitpid"); } /* Got one */ return retval; } int vsf_sysutil_wait_get_retval(const struct vsf_sysutil_wait_retval* p_waitret) { return p_waitret->syscall_retval; } int vsf_sysutil_wait_exited_normally( const struct vsf_sysutil_wait_retval* p_waitret) { int status = ((struct vsf_sysutil_wait_retval*) p_waitret)->exit_status; return WIFEXITED(status); } int vsf_sysutil_wait_get_exitcode(const struct vsf_sysutil_wait_retval* p_waitret) { int status; if (!vsf_sysutil_wait_exited_normally(p_waitret)) { bug("not a normal exit in vsf_sysutil_wait_get_exitcode"); } status = ((struct vsf_sysutil_wait_retval*) p_waitret)->exit_status; return WEXITSTATUS(status); } void vsf_sysutil_activate_keepalive(int fd) { int keepalive = 1; int retval = setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &keepalive, sizeof(keepalive)); if (retval != 0) { die("setsockopt: keepalive"); } } void vsf_sysutil_activate_reuseaddr(int fd) { int reuseaddr = 1; int retval = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr)); if (retval != 0) { die("setsockopt: reuseaddr"); } } void vsf_sysutil_set_nodelay(int fd) { int nodelay = 1; int retval = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &nodelay, sizeof(nodelay)); if (retval != 0) { die("setsockopt: nodelay"); } } void vsf_sysutil_activate_sigurg(int fd) { int retval = fcntl(fd, F_SETOWN, vsf_sysutil_getpid()); if (retval != 0) { die("fcntl"); } } void vsf_sysutil_activate_oobinline(int fd) { int oob_inline = 1; int retval = setsockopt(fd, SOL_SOCKET, SO_OOBINLINE, &oob_inline, sizeof(oob_inline)); if (retval != 0) { die("setsockopt: oobinline"); } } void vsf_sysutil_set_iptos_throughput(int fd) { int tos = IPTOS_THROUGHPUT; /* Ignore failure to set (maybe this IP stack demands privilege for this) */ (void) setsockopt(fd, IPPROTO_IP, IP_TOS, &tos, sizeof(tos)); } void vsf_sysutil_activate_linger(int fd) { int retval; struct linger the_linger; vsf_sysutil_memclr(&the_linger, sizeof(the_linger)); the_linger.l_onoff = 1; the_linger.l_linger = 60 * 10; retval = setsockopt(fd, SOL_SOCKET, SO_LINGER, &the_linger, sizeof(the_linger)); if (retval != 0) { die("setsockopt: linger"); } } void vsf_sysutil_deactivate_linger_failok(int fd) { struct linger the_linger; the_linger.l_onoff = 0; the_linger.l_linger = 0; (void) setsockopt(fd, SOL_SOCKET, SO_LINGER, &the_linger, sizeof(the_linger)); } void vsf_sysutil_activate_noblock(int fd) { int retval; int curr_flags = fcntl(fd, F_GETFL); if (vsf_sysutil_retval_is_error(curr_flags)) { die("fcntl"); } curr_flags |= O_NONBLOCK; retval = fcntl(fd, F_SETFL, curr_flags); if (retval != 0) { die("fcntl"); } } void vsf_sysutil_deactivate_noblock(int fd) { int retval; int curr_flags = fcntl(fd, F_GETFL); if (vsf_sysutil_retval_is_error(curr_flags)) { die("fcntl"); } curr_flags &= ~O_NONBLOCK; retval = fcntl(fd, F_SETFL, curr_flags); if (retval != 0) { die("fcntl"); } } int vsf_sysutil_recv_peek(const int fd, void* p_buf, unsigned int len) { while (1) { int retval = recv(fd, p_buf, len, MSG_PEEK); int saved_errno = errno; vsf_sysutil_check_pending_actions(kVSFSysUtilIO, retval, fd); if (retval < 0 && saved_errno == EINTR) { continue; } return retval; } } int vsf_sysutil_atoi(const char* p_str) { return atoi(p_str); } filesize_t vsf_sysutil_a_to_filesize_t(const char* p_str) { /* atoll() is C99 standard - but even modern FreeBSD, OpenBSD don't have * it, so we'll supply our own */ filesize_t result = 0; filesize_t mult = 1; unsigned int len = vsf_sysutil_strlen(p_str); unsigned int i; /* Bail if the number is excessively big (petabytes!) */ if (len > 15) { return 0; } for (i=0; i '9') { return 0; } val = the_char - '0'; val *= mult; result += val; mult *= 10; } return result; } const char* vsf_sysutil_ulong_to_str(unsigned long the_ulong) { static char ulong_buf[32]; (void) snprintf(ulong_buf, sizeof(ulong_buf), "%lu", the_ulong); return ulong_buf; } const char* vsf_sysutil_filesize_t_to_str(filesize_t the_filesize) { static char filesize_buf[32]; if (sizeof(long) == 8) { /* Avoid using non-standard %ll if we can */ (void) snprintf(filesize_buf, sizeof(filesize_buf), "%ld", (long) the_filesize); } else { (void) snprintf(filesize_buf, sizeof(filesize_buf), "%lld", the_filesize); } return filesize_buf; } const char* vsf_sysutil_double_to_str(double the_double) { static char double_buf[32]; (void) snprintf(double_buf, sizeof(double_buf), "%.2f", the_double); return double_buf; } const char* vsf_sysutil_uint_to_octal(unsigned int the_uint) { static char octal_buf[32]; if (the_uint == 0) { octal_buf[0] = '0'; octal_buf[1] = '\0'; } else { (void) snprintf(octal_buf, sizeof(octal_buf), "0%o", the_uint); } return octal_buf; } unsigned int vsf_sysutil_octal_to_uint(const char* p_str) { /* NOTE - avoiding using sscanf() parser */ unsigned int result = 0; int seen_non_zero_digit = 0; while (*p_str != '\0') { int digit = *p_str; if (!isdigit(digit) || digit > '7') { break; } if (digit != '0') { seen_non_zero_digit = 1; } if (seen_non_zero_digit) { result <<= 3; result += (digit - '0'); } p_str++; } return result; } int vsf_sysutil_toupper(int the_char) { return toupper((unsigned char) the_char); } int vsf_sysutil_isspace(int the_char) { return isspace((unsigned char) the_char); } int vsf_sysutil_isprint(int the_char) { /* From Solar - we know better than some libc's! Don't let any potential * control chars through */ unsigned char uc = (unsigned char) the_char; if (uc <= 31) { return 0; } if (uc == 177) { return 0; } if (uc >= 128 && uc <= 159) { return 0; } return isprint(the_char); } int vsf_sysutil_isalnum(int the_char) { return isalnum((unsigned char) the_char); } int vsf_sysutil_isdigit(int the_char) { return isdigit((unsigned char) the_char); } char* vsf_sysutil_getcwd(char* p_dest, const unsigned int buf_size) { char* p_retval; if (buf_size == 0) { return p_dest; } p_retval = getcwd(p_dest, buf_size); p_dest[buf_size - 1] = '\0'; return p_retval; } int vsf_sysutil_mkdir(const char* p_dirname, const unsigned int mode) { return mkdir(p_dirname, mode); } int vsf_sysutil_rmdir(const char* p_dirname) { return rmdir(p_dirname); } int vsf_sysutil_chdir(const char* p_dirname) { return chdir(p_dirname); } int vsf_sysutil_rename(const char* p_from, const char* p_to) { return rename(p_from, p_to); } struct vsf_sysutil_dir* vsf_sysutil_opendir(const char* p_dirname) { return (struct vsf_sysutil_dir*) opendir(p_dirname); } void vsf_sysutil_closedir(struct vsf_sysutil_dir* p_dir) { DIR* p_real_dir = (DIR*) p_dir; int retval = closedir(p_real_dir); if (retval != 0) { die("closedir"); } } const char* vsf_sysutil_next_dirent(struct vsf_sysutil_dir* p_dir) { DIR* p_real_dir = (DIR*) p_dir; struct dirent* p_dirent = readdir(p_real_dir); if (p_dirent == NULL) { return NULL; } return p_dirent->d_name; } unsigned int vsf_sysutil_strlen(const char* p_text) { size_t ret = strlen(p_text); /* A defense in depth measure. */ if (ret > INT_MAX / 8) { die("string suspiciously long"); } return (unsigned int) ret; } char* vsf_sysutil_strdup(const char* p_str) { return strdup(p_str); } void vsf_sysutil_memclr(void* p_dest, unsigned int size) { /* Safety */ if (size == 0) { return; } memset(p_dest, '\0', size); } void vsf_sysutil_memcpy(void* p_dest, const void* p_src, const unsigned int size) { /* Safety */ if (size == 0) { return; } /* Defense in depth */ if (size > INT_MAX) { die("possible negative value to memcpy?"); } memcpy(p_dest, p_src, size); } void vsf_sysutil_strcpy(char* p_dest, const char* p_src, unsigned int maxsize) { if (maxsize == 0) { return; } strncpy(p_dest, p_src, maxsize); p_dest[maxsize - 1] = '\0'; } int vsf_sysutil_memcmp(const void* p_src1, const void* p_src2, unsigned int size) { /* Safety */ if (size == 0) { return 0; } return memcmp(p_src1, p_src2, size); } int vsf_sysutil_strcmp(const char* p_src1, const char* p_src2) { return strcmp(p_src1, p_src2); } unsigned int vsf_sysutil_getpagesize(void) { static unsigned int s_page_size; if (s_page_size == 0) { s_page_size = getpagesize(); if (s_page_size == 0) { die("getpagesize"); } } return s_page_size; } static int vsf_sysutil_translate_memprot(const enum EVSFSysUtilMapPermission perm) { int retval = 0; switch (perm) { case kVSFSysUtilMapProtReadOnly: retval = PROT_READ; break; case kVSFSysUtilMapProtNone: retval = PROT_NONE; break; default: bug("bad value in vsf_sysutil_translate_memprot"); break; } return retval; } void vsf_sysutil_memprotect(void* p_addr, unsigned int len, const enum EVSFSysUtilMapPermission perm) { int prot = vsf_sysutil_translate_memprot(perm); int retval = mprotect(p_addr, len, prot); if (retval != 0) { die("mprotect"); } } void vsf_sysutil_memunmap(void* p_start, unsigned int length) { int retval = munmap(p_start, length); if (retval != 0) { die("munmap"); } } static int vsf_sysutil_translate_openmode(const enum EVSFSysUtilOpenMode mode) { int retval = 0; switch (mode) { case kVSFSysUtilOpenReadOnly: retval = O_RDONLY; break; case kVSFSysUtilOpenWriteOnly: retval = O_WRONLY; break; case kVSFSysUtilOpenReadWrite: retval = O_RDWR; break; default: bug("bad mode in vsf_sysutil_translate_openmode"); break; } return retval; } int vsf_sysutil_open_file(const char* p_filename, const enum EVSFSysUtilOpenMode mode) { return open(p_filename, vsf_sysutil_translate_openmode(mode) | O_NONBLOCK); } int vsf_sysutil_create_file_exclusive(const char* p_filename) { /* umask() also contributes to end mode */ return open(p_filename, O_CREAT | O_EXCL | O_WRONLY | O_APPEND, tunable_file_open_mode); } int vsf_sysutil_create_or_open_file(const char* p_filename, unsigned int mode) { return open(p_filename, O_CREAT | O_WRONLY | O_NONBLOCK, mode); } int vsf_sysutil_create_or_open_file_append(const char* p_filename, unsigned int mode) { return open(p_filename, O_CREAT | O_WRONLY | O_NONBLOCK | O_APPEND, mode); } void vsf_sysutil_dupfd2(int old_fd, int new_fd) { int retval; if (old_fd == new_fd) { return; } retval = dup2(old_fd, new_fd); if (retval != new_fd) { die("dup2"); } } void vsf_sysutil_close(int fd) { while (1) { int retval = close(fd); if (retval != 0) { if (errno == EINTR) { vsf_sysutil_check_pending_actions(kVSFSysUtilUnknown, 0, 0); continue; } die("close"); } return; } } int vsf_sysutil_close_failok(int fd) { return close(fd); } int vsf_sysutil_unlink(const char* p_dead) { return unlink(p_dead); } int vsf_sysutil_write_access(const char* p_filename) { int retval = access(p_filename, W_OK); return (retval == 0); } static void vsf_sysutil_alloc_statbuf(struct vsf_sysutil_statbuf** p_ptr) { if (*p_ptr == NULL) { *p_ptr = vsf_sysutil_malloc(sizeof(struct stat)); } } void vsf_sysutil_fstat(int fd, struct vsf_sysutil_statbuf** p_ptr) { int retval; vsf_sysutil_alloc_statbuf(p_ptr); retval = fstat(fd, (struct stat*) (*p_ptr)); if (retval != 0) { die("fstat"); } } int vsf_sysutil_stat(const char* p_name, struct vsf_sysutil_statbuf** p_ptr) { vsf_sysutil_alloc_statbuf(p_ptr); return stat(p_name, (struct stat*) (*p_ptr)); } int vsf_sysutil_lstat(const char* p_name, struct vsf_sysutil_statbuf** p_ptr) { vsf_sysutil_alloc_statbuf(p_ptr); return lstat(p_name, (struct stat*) (*p_ptr)); } void vsf_sysutil_dir_stat(const struct vsf_sysutil_dir* p_dir, struct vsf_sysutil_statbuf** p_ptr) { int fd = dirfd((DIR*) p_dir); vsf_sysutil_fstat(fd, p_ptr); } int vsf_sysutil_statbuf_is_regfile(const struct vsf_sysutil_statbuf* p_stat) { const struct stat* p_realstat = (const struct stat*) p_stat; return S_ISREG(p_realstat->st_mode); } int vsf_sysutil_statbuf_is_symlink(const struct vsf_sysutil_statbuf* p_stat) { const struct stat* p_realstat = (const struct stat*) p_stat; return S_ISLNK(p_realstat->st_mode); } int vsf_sysutil_statbuf_is_socket(const struct vsf_sysutil_statbuf* p_stat) { const struct stat* p_realstat = (const struct stat*) p_stat; return S_ISSOCK(p_realstat->st_mode); } int vsf_sysutil_statbuf_is_dir(const struct vsf_sysutil_statbuf* p_stat) { const struct stat* p_realstat = (const struct stat*) p_stat; return S_ISDIR(p_realstat->st_mode); } const char* vsf_sysutil_statbuf_get_perms(const struct vsf_sysutil_statbuf* p_statbuf) { static char perms[11]; int i; const struct stat* p_stat = (const struct stat*) p_statbuf; for (i=0; i<10; i++) { perms[i] = '-'; } perms[0] = '?'; switch (p_stat->st_mode & S_IFMT) { case S_IFREG: perms[0] = '-'; break; case S_IFDIR: perms[0] = 'd'; break; case S_IFLNK: perms[0] = 'l'; break; case S_IFIFO: perms[0] = 'p'; break; case S_IFSOCK: perms[0] = 's'; break; case S_IFCHR: perms[0] = 'c'; break; case S_IFBLK: perms[0] = 'b'; break; default: break; } if (p_stat->st_mode & S_IRUSR) perms[1] = 'r'; if (p_stat->st_mode & S_IWUSR) perms[2] = 'w'; if (p_stat->st_mode & S_IXUSR) perms[3] = 'x'; if (p_stat->st_mode & S_IRGRP) perms[4] = 'r'; if (p_stat->st_mode & S_IWGRP) perms[5] = 'w'; if (p_stat->st_mode & S_IXGRP) perms[6] = 'x'; if (p_stat->st_mode & S_IROTH) perms[7] = 'r'; if (p_stat->st_mode & S_IWOTH) perms[8] = 'w'; if (p_stat->st_mode & S_IXOTH) perms[9] = 'x'; if (p_stat->st_mode & S_ISUID) perms[3] = (perms[3] == 'x') ? 's' : 'S'; if (p_stat->st_mode & S_ISGID) perms[6] = (perms[6] == 'x') ? 's' : 'S'; if (p_stat->st_mode & S_ISVTX) perms[9] = (perms[9] == 'x') ? 't' : 'T'; perms[10] = '\0'; return perms; } const char* vsf_sysutil_statbuf_get_date(const struct vsf_sysutil_statbuf* p_statbuf, int use_localtime, long curr_time) { static char datebuf[64]; int retval; struct tm* p_tm; const struct stat* p_stat = (const struct stat*) p_statbuf; const char* p_date_format = "%b %d %H:%M"; if (!use_localtime) { p_tm = gmtime(&p_stat->st_mtime); } else { p_tm = localtime(&p_stat->st_mtime); } /* Is this a future or 6 months old date? If so, we drop to year format */ if (p_stat->st_mtime > curr_time || (curr_time - p_stat->st_mtime) > 60*60*24*182) { p_date_format = "%b %d %Y"; } retval = strftime(datebuf, sizeof(datebuf), p_date_format, p_tm); datebuf[sizeof(datebuf)-1] = '\0'; if (retval == 0) { die("strftime"); } return datebuf; } const char* vsf_sysutil_statbuf_get_numeric_date( const struct vsf_sysutil_statbuf* p_statbuf, int use_localtime) { static char datebuf[15]; const struct stat* p_stat = (const struct stat*) p_statbuf; struct tm* p_tm; int retval; if (!use_localtime) { p_tm = gmtime(&p_stat->st_mtime); } else { p_tm = localtime(&p_stat->st_mtime); } retval = strftime(datebuf, sizeof(datebuf), "%Y%m%d%H%M%S", p_tm); if (retval == 0) { die("strftime"); } return datebuf; } filesize_t vsf_sysutil_statbuf_get_size(const struct vsf_sysutil_statbuf* p_statbuf) { const struct stat* p_stat = (const struct stat*) p_statbuf; if (p_stat->st_size < 0) { die("invalid inode size in vsf_sysutil_statbuf_get_size"); } return p_stat->st_size; } int vsf_sysutil_statbuf_get_uid(const struct vsf_sysutil_statbuf* p_statbuf) { const struct stat* p_stat = (const struct stat*) p_statbuf; return p_stat->st_uid; } int vsf_sysutil_statbuf_get_gid(const struct vsf_sysutil_statbuf* p_statbuf) { const struct stat* p_stat = (const struct stat*) p_statbuf; return p_stat->st_gid; } unsigned int vsf_sysutil_statbuf_get_links(const struct vsf_sysutil_statbuf* p_statbuf) { const struct stat* p_stat = (const struct stat*) p_statbuf; return p_stat->st_nlink; } int vsf_sysutil_statbuf_is_readable_other( const struct vsf_sysutil_statbuf* p_statbuf) { const struct stat* p_stat = (const struct stat*) p_statbuf; if (p_stat->st_mode & S_IROTH) { return 1; } return 0; } const char* vsf_sysutil_statbuf_get_sortkey_mtime( const struct vsf_sysutil_statbuf* p_statbuf) { static char intbuf[32]; const struct stat* p_stat = (const struct stat*) p_statbuf; /* This slight hack function must return a character date format such that * more recent dates appear later in the alphabet! Most notably, we must * make sure we pad to the same length with 0's */ snprintf(intbuf, sizeof(intbuf), "%030ld", (long) p_stat->st_mtime); return intbuf; } void vsf_sysutil_fchown(const int fd, const int uid, const int gid) { if (fchown(fd, uid, gid) != 0) { die("fchown"); } } void vsf_sysutil_fchmod(const int fd, unsigned int mode) { mode = mode & 0777; if (fchmod(fd, mode)) { die("fchmod"); } } int vsf_sysutil_chmod(const char* p_filename, unsigned int mode) { /* Safety: mask "mode" to just access permissions, e.g. no suid setting! */ mode = mode & 0777; return chmod(p_filename, mode); } int vsf_sysutil_lock_file_write(int fd) { return lock_internal(fd, F_WRLCK); } int vsf_sysutil_lock_file_read(int fd) { return lock_internal(fd, F_RDLCK); } static int lock_internal(int fd, int lock_type) { struct flock the_lock; int retval; int saved_errno; vsf_sysutil_memclr(&the_lock, sizeof(the_lock)); the_lock.l_type = lock_type; the_lock.l_whence = SEEK_SET; the_lock.l_start = 0; the_lock.l_len = 0; do { retval = fcntl(fd, F_SETLKW, &the_lock); saved_errno = errno; vsf_sysutil_check_pending_actions(kVSFSysUtilUnknown, 0, 0); } while (retval < 0 && saved_errno == EINTR); return retval; } void vsf_sysutil_unlock_file(int fd) { int retval; struct flock the_lock; vsf_sysutil_memclr(&the_lock, sizeof(the_lock)); the_lock.l_type = F_UNLCK; the_lock.l_whence = SEEK_SET; the_lock.l_start = 0; the_lock.l_len = 0; retval = fcntl(fd, F_SETLK, &the_lock); if (retval != 0) { die("fcntl"); } } int vsf_sysutil_readlink(const char* p_filename, char* p_dest, unsigned int bufsiz) { int retval; if (bufsiz == 0) { return -1; } retval = readlink(p_filename, p_dest, bufsiz - 1); if (retval < 0) { return retval; } /* Ensure buffer is NULL terminated; readlink(2) doesn't do that */ p_dest[retval] = '\0'; return retval; } int vsf_sysutil_retval_is_error(int retval) { if (retval < 0) { return 1; } return 0; } enum EVSFSysUtilError vsf_sysutil_get_error(void) { enum EVSFSysUtilError retval = kVSFSysUtilErrUnknown; switch (errno) { case EADDRINUSE: retval = kVSFSysUtilErrADDRINUSE; break; case ENOSYS: retval = kVSFSysUtilErrNOSYS; break; case EINTR: retval = kVSFSysUtilErrINTR; break; case EINVAL: retval = kVSFSysUtilErrINVAL; break; case EOPNOTSUPP: retval = kVSFSysUtilErrOPNOTSUPP; break; case EACCES: retval = kVSFSysUtilErrACCES; break; case ENOENT: retval = kVSFSysUtilErrNOENT; break; default: break; } return retval; } int vsf_sysutil_get_ipv4_sock(void) { int retval = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); if (retval < 0) { die("socket"); } return retval; } int vsf_sysutil_get_ipv6_sock(void) { int retval = socket(PF_INET6, SOCK_STREAM, IPPROTO_TCP); if (retval < 0) { die("socket"); } return retval; } struct vsf_sysutil_socketpair_retval vsf_sysutil_unix_stream_socketpair(void) { struct vsf_sysutil_socketpair_retval retval; int the_sockets[2]; int sys_retval = socketpair(PF_UNIX, SOCK_STREAM, 0, the_sockets); if (sys_retval != 0) { die("socketpair"); } retval.socket_one = the_sockets[0]; retval.socket_two = the_sockets[1]; return retval; } int vsf_sysutil_bind(int fd, const struct vsf_sysutil_sockaddr* p_sockptr) { const struct sockaddr* p_sockaddr = &p_sockptr->u.u_sockaddr; int len = 0; if (p_sockaddr->sa_family == AF_INET) { len = sizeof(struct sockaddr_in); } else if (p_sockaddr->sa_family == AF_INET6) { len = sizeof(struct sockaddr_in6); } else { die("can only support ipv4 and ipv6 currently"); } return bind(fd, p_sockaddr, len); } int vsf_sysutil_listen(int fd, const unsigned int backlog) { int retval = listen(fd, backlog); if (vsf_sysutil_retval_is_error(retval) && vsf_sysutil_get_error() != kVSFSysUtilErrADDRINUSE) { die("listen"); } return retval; } /* Warning: callers of this function assume it does NOT make use of any * non re-entrant calls such as malloc(). */ int vsf_sysutil_accept_timeout(int fd, struct vsf_sysutil_sockaddr* p_sockaddr, unsigned int wait_seconds) { struct vsf_sysutil_sockaddr remote_addr; int retval; int saved_errno; fd_set accept_fdset; struct timeval timeout; socklen_t socklen = sizeof(remote_addr); if (p_sockaddr) { vsf_sysutil_memclr(p_sockaddr, sizeof(*p_sockaddr)); } if (wait_seconds > 0) { FD_ZERO(&accept_fdset); FD_SET(fd, &accept_fdset); timeout.tv_sec = wait_seconds; timeout.tv_usec = 0; do { retval = select(fd + 1, &accept_fdset, NULL, NULL, &timeout); saved_errno = errno; vsf_sysutil_check_pending_actions(kVSFSysUtilUnknown, 0, 0); } while (retval < 0 && saved_errno == EINTR); if (retval <= 0) { if (retval == 0) { errno = EAGAIN; } return -1; } } retval = accept(fd, &remote_addr.u.u_sockaddr, &socklen); vsf_sysutil_check_pending_actions(kVSFSysUtilUnknown, 0, 0); if (retval < 0) { return retval; } /* FreeBSD bug / paranoia: ai32@drexel.edu */ if (socklen == 0) { return -1; } if (remote_addr.u.u_sockaddr.sa_family != AF_INET && remote_addr.u.u_sockaddr.sa_family != AF_INET6) { die("can only support ipv4 and ipv6 currently"); } if (p_sockaddr) { if (remote_addr.u.u_sockaddr.sa_family == AF_INET) { vsf_sysutil_memclr(&remote_addr.u.u_sockaddr_in.sin_zero, sizeof(remote_addr.u.u_sockaddr_in.sin_zero)); vsf_sysutil_memcpy(p_sockaddr, &remote_addr.u.u_sockaddr_in, sizeof(remote_addr.u.u_sockaddr_in)); } else { vsf_sysutil_memcpy(p_sockaddr, &remote_addr.u.u_sockaddr_in6, sizeof(remote_addr.u.u_sockaddr_in6)); } } return retval; } int vsf_sysutil_connect_timeout(int fd, const struct vsf_sysutil_sockaddr* p_addr, unsigned int wait_seconds) { const struct sockaddr* p_sockaddr = &p_addr->u.u_sockaddr; unsigned int addrlen = 0; int retval; int saved_errno; if (p_sockaddr->sa_family == AF_INET) { addrlen = sizeof(p_addr->u.u_sockaddr_in); } else if (p_sockaddr->sa_family == AF_INET6) { addrlen = sizeof(p_addr->u.u_sockaddr_in6); } else { die("can only support ipv4 and ipv6 currently"); } if (wait_seconds > 0) { vsf_sysutil_activate_noblock(fd); } retval = connect(fd, p_sockaddr, addrlen); if (retval < 0 && errno == EINPROGRESS) { fd_set connect_fdset; struct timeval timeout; FD_ZERO(&connect_fdset); FD_SET(fd, &connect_fdset); timeout.tv_sec = wait_seconds; timeout.tv_usec = 0; do { retval = select(fd + 1, NULL, &connect_fdset, NULL, &timeout); saved_errno = errno; vsf_sysutil_check_pending_actions(kVSFSysUtilUnknown, 0, 0); } while (retval < 0 && saved_errno == EINTR); if (retval <= 0) { if (retval == 0) { errno = EAGAIN; } retval = -1; } else { socklen_t socklen = sizeof(retval); int sockoptret = getsockopt(fd, SOL_SOCKET, SO_ERROR, &retval, &socklen); if (sockoptret != 0) { die("getsockopt"); } if (retval != 0) { errno = retval; retval = -1; } } } if (wait_seconds > 0) { vsf_sysutil_deactivate_noblock(fd); } return retval; } void vsf_sysutil_getsockname(int fd, struct vsf_sysutil_sockaddr** p_sockptr) { struct vsf_sysutil_sockaddr the_addr; int retval; socklen_t socklen = sizeof(the_addr); vsf_sysutil_sockaddr_clear(p_sockptr); retval = getsockname(fd, &the_addr.u.u_sockaddr, &socklen); if (retval != 0) { die("getsockname"); } if (the_addr.u.u_sockaddr.sa_family != AF_INET && the_addr.u.u_sockaddr.sa_family != AF_INET6) { die("can only support ipv4 and ipv6 currently"); } vsf_sysutil_sockaddr_alloc(p_sockptr); if (socklen > sizeof(the_addr)) { socklen = sizeof(the_addr); } vsf_sysutil_memcpy(*p_sockptr, &the_addr, socklen); } void vsf_sysutil_getpeername(int fd, struct vsf_sysutil_sockaddr** p_sockptr) { struct vsf_sysutil_sockaddr the_addr; int retval; socklen_t socklen = sizeof(the_addr); vsf_sysutil_sockaddr_clear(p_sockptr); retval = getpeername(fd, &the_addr.u.u_sockaddr, &socklen); if (retval != 0) { die("getpeername"); } if (the_addr.u.u_sockaddr.sa_family != AF_INET && the_addr.u.u_sockaddr.sa_family != AF_INET6) { die("can only support ipv4 and ipv6 currently"); } vsf_sysutil_sockaddr_alloc(p_sockptr); if (socklen > sizeof(the_addr)) { socklen = sizeof(the_addr); } vsf_sysutil_memcpy(*p_sockptr, &the_addr, socklen); } void vsf_sysutil_shutdown_failok(int fd) { /* SHUT_RDWR is a relatively new addition */ #ifndef SHUT_RDWR #define SHUT_RDWR 2 #endif (void) shutdown(fd, SHUT_RDWR); } void vsf_sysutil_shutdown_read_failok(int fd) { /* SHUT_RD is a relatively new addition */ #ifndef SHUT_RD #define SHUT_RD 0 #endif (void) shutdown(fd, SHUT_RD); } void vsf_sysutil_sockaddr_clear(struct vsf_sysutil_sockaddr** p_sockptr) { if (*p_sockptr != NULL) { vsf_sysutil_free(*p_sockptr); *p_sockptr = NULL; } } void vsf_sysutil_sockaddr_alloc(struct vsf_sysutil_sockaddr** p_sockptr) { vsf_sysutil_sockaddr_clear(p_sockptr); *p_sockptr = vsf_sysutil_malloc(sizeof(**p_sockptr)); vsf_sysutil_memclr(*p_sockptr, sizeof(**p_sockptr)); } void vsf_sysutil_sockaddr_alloc_ipv4(struct vsf_sysutil_sockaddr** p_sockptr) { vsf_sysutil_sockaddr_alloc(p_sockptr); (*p_sockptr)->u.u_sockaddr.sa_family = AF_INET; } void vsf_sysutil_sockaddr_alloc_ipv6(struct vsf_sysutil_sockaddr** p_sockptr) { vsf_sysutil_sockaddr_alloc(p_sockptr); (*p_sockptr)->u.u_sockaddr.sa_family = AF_INET6; } void vsf_sysutil_sockaddr_clone(struct vsf_sysutil_sockaddr** p_sockptr, const struct vsf_sysutil_sockaddr* p_src) { struct vsf_sysutil_sockaddr* p_sockaddr = 0; vsf_sysutil_sockaddr_alloc(p_sockptr); p_sockaddr = *p_sockptr; if (p_src->u.u_sockaddr.sa_family == AF_INET) { p_sockaddr->u.u_sockaddr.sa_family = AF_INET; vsf_sysutil_memcpy(&p_sockaddr->u.u_sockaddr_in.sin_addr, &p_src->u.u_sockaddr_in.sin_addr, sizeof(p_sockaddr->u.u_sockaddr_in.sin_addr)); } else if (p_src->u.u_sockaddr.sa_family == AF_INET6) { p_sockaddr->u.u_sockaddr.sa_family = AF_INET6; vsf_sysutil_memcpy(&p_sockaddr->u.u_sockaddr_in6.sin6_addr, &p_src->u.u_sockaddr_in6.sin6_addr, sizeof(p_sockaddr->u.u_sockaddr_in6.sin6_addr)); p_sockaddr->u.u_sockaddr_in6.sin6_scope_id = p_src->u.u_sockaddr_in6.sin6_scope_id; } else { die("can only support ipv4 and ipv6 currently"); } } int vsf_sysutil_sockaddr_addr_equal(const struct vsf_sysutil_sockaddr* p1, const struct vsf_sysutil_sockaddr* p2) { int family1 = p1->u.u_sockaddr.sa_family; int family2 = p2->u.u_sockaddr.sa_family; if (family1 != family2) { if (family1 == AF_INET && family2 == AF_INET6) { const void* p_ipv4_addr = vsf_sysutil_sockaddr_ipv6_v4(p2); if (p_ipv4_addr && !vsf_sysutil_memcmp(p_ipv4_addr, &p1->u.u_sockaddr_in.sin_addr, sizeof(p1->u.u_sockaddr_in.sin_addr))) { return 1; } } else if (family1 == AF_INET6 && family2 == AF_INET) { const void* p_ipv4_addr = vsf_sysutil_sockaddr_ipv6_v4(p1); if (p_ipv4_addr && !vsf_sysutil_memcmp(p_ipv4_addr, &p2->u.u_sockaddr_in.sin_addr, sizeof(p2->u.u_sockaddr_in.sin_addr))) { return 1; } } return 0; } if (family1 == AF_INET) { if (vsf_sysutil_memcmp(&p1->u.u_sockaddr_in.sin_addr, &p2->u.u_sockaddr_in.sin_addr, sizeof(p1->u.u_sockaddr_in.sin_addr)) == 0) { return 1; } } else if (family1 == AF_INET6) { if (vsf_sysutil_memcmp(&p1->u.u_sockaddr_in6.sin6_addr, &p2->u.u_sockaddr_in6.sin6_addr, sizeof(p1->u.u_sockaddr_in6.sin6_addr)) == 0) { return 1; } } return 0; } int vsf_sysutil_sockaddr_is_ipv6(const struct vsf_sysutil_sockaddr* p_sockaddr) { if (p_sockaddr->u.u_sockaddr.sa_family == AF_INET6) { return 1; } return 0; } void vsf_sysutil_sockaddr_set_ipv4addr(struct vsf_sysutil_sockaddr* p_sockptr, const unsigned char* p_raw) { if (p_sockptr->u.u_sockaddr.sa_family == AF_INET) { vsf_sysutil_memcpy(&p_sockptr->u.u_sockaddr_in.sin_addr, p_raw, sizeof(p_sockptr->u.u_sockaddr_in.sin_addr)); } else if (p_sockptr->u.u_sockaddr.sa_family == AF_INET6) { static struct vsf_sysutil_sockaddr* s_p_sockaddr; vsf_sysutil_sockaddr_alloc_ipv4(&s_p_sockaddr); vsf_sysutil_memcpy(&s_p_sockaddr->u.u_sockaddr_in.sin_addr, p_raw, sizeof(s_p_sockaddr->u.u_sockaddr_in.sin_addr)); vsf_sysutil_memcpy(&p_sockptr->u.u_sockaddr_in6.sin6_addr, vsf_sysutil_sockaddr_ipv4_v6(s_p_sockaddr), sizeof(p_sockptr->u.u_sockaddr_in6.sin6_addr)); } else { bug("bad family"); } } void vsf_sysutil_sockaddr_set_ipv6addr(struct vsf_sysutil_sockaddr* p_sockptr, const unsigned char* p_raw) { if (p_sockptr->u.u_sockaddr.sa_family == AF_INET6) { vsf_sysutil_memcpy(&p_sockptr->u.u_sockaddr_in6.sin6_addr, p_raw, sizeof(p_sockptr->u.u_sockaddr_in6.sin6_addr)); } else { bug("bad family"); } } const void* vsf_sysutil_sockaddr_ipv6_v4(const struct vsf_sysutil_sockaddr* p_addr) { static unsigned char pattern[12] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF }; const unsigned char* p_addr_start; if (p_addr->u.u_sockaddr.sa_family != AF_INET6) { return 0; } if (vsf_sysutil_memcmp(pattern, &p_addr->u.u_sockaddr_in6.sin6_addr, 12)) { return 0; } p_addr_start = (const unsigned char*)&p_addr->u.u_sockaddr_in6.sin6_addr; return &p_addr_start[12]; } const void* vsf_sysutil_sockaddr_ipv4_v6(const struct vsf_sysutil_sockaddr* p_addr) { static unsigned char ret[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF }; if (p_addr->u.u_sockaddr.sa_family != AF_INET) { return 0; } vsf_sysutil_memcpy(&ret[12], &p_addr->u.u_sockaddr_in.sin_addr, 4); return ret; } void* vsf_sysutil_sockaddr_get_raw_addr(struct vsf_sysutil_sockaddr* p_sockptr) { if (p_sockptr->u.u_sockaddr.sa_family == AF_INET) { return &p_sockptr->u.u_sockaddr_in.sin_addr; } else if (p_sockptr->u.u_sockaddr.sa_family == AF_INET6) { return &p_sockptr->u.u_sockaddr_in6.sin6_addr; } else { bug("bad family"); } return 0; } unsigned int vsf_sysutil_get_ipaddr_size(void) { struct vsf_sysutil_sockaddr addr; unsigned int size = sizeof(addr.u.u_sockaddr_in.sin_addr); unsigned int size2 = sizeof(addr.u.u_sockaddr_in6.sin6_addr); if (size2 > size) { size = size2; } return size; } int vsf_sysutil_get_ipsock(const struct vsf_sysutil_sockaddr* p_addr) { if (p_addr->u.u_sockaddr.sa_family == AF_INET) { return vsf_sysutil_get_ipv4_sock(); } else if (p_addr->u.u_sockaddr.sa_family == AF_INET6) { return vsf_sysutil_get_ipv6_sock(); } else { bug("bad family"); } return -1; } void vsf_sysutil_sockaddr_set_any(struct vsf_sysutil_sockaddr* p_sockaddr) { if (p_sockaddr->u.u_sockaddr.sa_family == AF_INET) { vsf_sysutil_memclr(&p_sockaddr->u.u_sockaddr_in.sin_addr, sizeof(p_sockaddr->u.u_sockaddr_in.sin_addr)); } else if (p_sockaddr->u.u_sockaddr.sa_family == AF_INET6) { vsf_sysutil_memclr(&p_sockaddr->u.u_sockaddr_in6.sin6_addr, sizeof(p_sockaddr->u.u_sockaddr_in6.sin6_addr)); } else { bug("bad family"); } } unsigned short vsf_sysutil_sockaddr_get_port(const struct vsf_sysutil_sockaddr* p_sockptr) { if (p_sockptr->u.u_sockaddr.sa_family == AF_INET) { return ntohs(p_sockptr->u.u_sockaddr_in.sin_port); } else if (p_sockptr->u.u_sockaddr.sa_family == AF_INET6) { return ntohs(p_sockptr->u.u_sockaddr_in6.sin6_port); } else { bug("bad family"); } /* NOTREACHED */ return 0; } void vsf_sysutil_sockaddr_set_port(struct vsf_sysutil_sockaddr* p_sockptr, unsigned short the_port) { if (p_sockptr->u.u_sockaddr.sa_family == AF_INET) { p_sockptr->u.u_sockaddr_in.sin_port = htons(the_port); } else if (p_sockptr->u.u_sockaddr.sa_family == AF_INET6) { p_sockptr->u.u_sockaddr_in6.sin6_port = htons(the_port); } else { bug("bad family"); } } int vsf_sysutil_is_port_reserved(unsigned short the_port) { if (the_port < IPPORT_RESERVED) { return 1; } return 0; } const char* vsf_sysutil_inet_ntop(const struct vsf_sysutil_sockaddr* p_sockptr) { const struct sockaddr* p_sockaddr = &p_sockptr->u.u_sockaddr; if (p_sockaddr->sa_family == AF_INET) { return inet_ntoa(p_sockptr->u.u_sockaddr_in.sin_addr); } else if (p_sockaddr->sa_family == AF_INET6) { static char inaddr_buf[64]; const char* p_ret = inet_ntop(AF_INET6, &p_sockptr->u.u_sockaddr_in6.sin6_addr, inaddr_buf, sizeof(inaddr_buf)); inaddr_buf[sizeof(inaddr_buf) - 1] = '\0'; if (p_ret == NULL) { inaddr_buf[0] = '\0'; } return inaddr_buf; } else { die("can only support ipv4 and ipv6 currently"); return 0; } } const char* vsf_sysutil_inet_ntoa(const void* p_raw_addr) { return inet_ntoa(*((struct in_addr*)p_raw_addr)); } int vsf_sysutil_inet_aton(const char* p_text, struct vsf_sysutil_sockaddr* p_addr) { struct in_addr sin_addr; if (p_addr->u.u_sockaddr.sa_family != AF_INET) { bug("bad family"); } if (inet_aton(p_text, &sin_addr)) { vsf_sysutil_memcpy(&p_addr->u.u_sockaddr_in.sin_addr, &sin_addr, sizeof(p_addr->u.u_sockaddr_in.sin_addr)); return 1; } else { return 0; } } void vsf_sysutil_dns_resolve(struct vsf_sysutil_sockaddr** p_sockptr, const char* p_name) { struct hostent* hent = gethostbyname(p_name); if (hent == NULL) { die2("cannot resolve host:", p_name); } vsf_sysutil_sockaddr_clear(p_sockptr); if (hent->h_addrtype == AF_INET) { unsigned int len = hent->h_length; if (len > sizeof((*p_sockptr)->u.u_sockaddr_in.sin_addr)) { len = sizeof((*p_sockptr)->u.u_sockaddr_in.sin_addr); } vsf_sysutil_sockaddr_alloc_ipv4(p_sockptr); vsf_sysutil_memcpy(&(*p_sockptr)->u.u_sockaddr_in.sin_addr, hent->h_addr_list[0], len); } else if (hent->h_addrtype == AF_INET6) { unsigned int len = hent->h_length; if (len > sizeof((*p_sockptr)->u.u_sockaddr_in6.sin6_addr)) { len = sizeof((*p_sockptr)->u.u_sockaddr_in6.sin6_addr); } vsf_sysutil_sockaddr_alloc_ipv6(p_sockptr); vsf_sysutil_memcpy(&(*p_sockptr)->u.u_sockaddr_in6.sin6_addr, hent->h_addr_list[0], len); } else { die("gethostbyname(): neither IPv4 nor IPv6"); } } struct vsf_sysutil_user* vsf_sysutil_getpwuid(const int uid) { if (uid < 0) { bug("negative uid in vsf_sysutil_getpwuid"); } return (struct vsf_sysutil_user*) getpwuid((unsigned int) uid); } struct vsf_sysutil_user* vsf_sysutil_getpwnam(const char* p_user) { return (struct vsf_sysutil_user*) getpwnam(p_user); } const char* vsf_sysutil_user_getname(const struct vsf_sysutil_user* p_user) { const struct passwd* p_passwd = (const struct passwd*) p_user; return p_passwd->pw_name; } const char* vsf_sysutil_user_get_homedir(const struct vsf_sysutil_user* p_user) { const struct passwd* p_passwd = (const struct passwd*) p_user; return p_passwd->pw_dir; } int vsf_sysutil_user_getuid(const struct vsf_sysutil_user* p_user) { const struct passwd* p_passwd = (const struct passwd*) p_user; return p_passwd->pw_uid; } int vsf_sysutil_user_getgid(const struct vsf_sysutil_user* p_user) { const struct passwd* p_passwd = (const struct passwd*) p_user; return p_passwd->pw_gid; } struct vsf_sysutil_group* vsf_sysutil_getgrgid(const int gid) { if (gid < 0) { die("negative gid in vsf_sysutil_getgrgid"); } return (struct vsf_sysutil_group*) getgrgid((unsigned int) gid); } const char* vsf_sysutil_group_getname(const struct vsf_sysutil_group* p_group) { const struct group* p_grp = (const struct group*) p_group; return p_grp->gr_name; } unsigned char vsf_sysutil_get_random_byte(void) { static int seeded; unsigned int uint_res; unsigned char c1, c2, c3, c4; if (!seeded) { struct timeval tv; int retval = gettimeofday(&tv, NULL); if (retval != 0) { die("gettimeofday"); } srand((unsigned)tv.tv_usec); seeded = 1; } uint_res = rand(); c1 = uint_res & 0x000000ff; c2 = (uint_res >> 8) & 0x000000ff; c3 = (uint_res >> 16) & 0x000000ff; c4 = (uint_res >> 24) & 0x000000ff; return c1 ^ c2 ^ c3 ^ c4; } int vsf_sysutil_running_as_root(void) { return (getuid() == 0); } void vsf_sysutil_setuid(const struct vsf_sysutil_user* p_user) { const struct passwd* p_passwd = (const struct passwd*) p_user; vsf_sysutil_setuid_numeric(p_passwd->pw_uid); } void vsf_sysutil_setuid_numeric(int uid) { int retval = setuid(uid); if (retval != 0) { die("setuid"); } } void vsf_sysutil_setgid(const struct vsf_sysutil_user* p_user) { const struct passwd* p_passwd = (const struct passwd*) p_user; vsf_sysutil_setgid_numeric(p_passwd->pw_gid); } void vsf_sysutil_setgid_numeric(int gid) { int retval = setgid(gid); if (retval != 0) { die("setgid"); } } int vsf_sysutil_geteuid(void) { int retval = geteuid(); if (retval < 0) { die("geteuid"); } return retval; } int vsf_sysutil_getegid(void) { int retval = getegid(); if (retval < 0) { die("getegid"); } return retval; } void vsf_sysutil_seteuid(const struct vsf_sysutil_user* p_user) { const struct passwd* p_passwd = (const struct passwd*) p_user; vsf_sysutil_seteuid_numeric(p_passwd->pw_uid); } void vsf_sysutil_setegid(const struct vsf_sysutil_user* p_user) { const struct passwd* p_passwd = (const struct passwd*) p_user; vsf_sysutil_setegid_numeric(p_passwd->pw_gid); } void vsf_sysutil_seteuid_numeric(int uid) { /* setreuid() would seem to be more portable than seteuid() */ int retval = setreuid(-1, uid); if (retval != 0) { die("seteuid"); } } void vsf_sysutil_setegid_numeric(int gid) { /* setregid() would seem to be more portable than setegid() */ int retval = setregid(-1, gid); if (retval != 0) { die("setegid"); } } void vsf_sysutil_clear_supp_groups(void) { int retval = setgroups(0, NULL); if (retval != 0) { die("setgroups"); } } void vsf_sysutil_initgroups(const struct vsf_sysutil_user* p_user) { const struct passwd* p_passwd = (const struct passwd*) p_user; int retval = initgroups(p_passwd->pw_name, p_passwd->pw_gid); if (retval != 0) { die("initgroups"); } } void vsf_sysutil_chroot(const char* p_root_path) { int retval = chroot(p_root_path); if (retval != 0) { die("chroot"); } } unsigned int vsf_sysutil_get_umask(void) { return s_current_umask; } void vsf_sysutil_set_umask(unsigned int new_umask) { s_current_umask = (new_umask & 0777); (void) umask(s_current_umask); } void vsf_sysutil_make_session_leader(void) { /* This makes us the leader if we are not already */ (void) setsid(); /* Check we're the leader */ if ((int) vsf_sysutil_getpid() != getpgrp()) { die("not session leader"); } } void vsf_sysutil_reopen_standard_fds(void) { /* This reopens STDIN, STDOUT and STDERR to /dev/null */ int fd; if ((fd = open("/dev/null", O_RDWR, 0)) < 0) { goto error; } vsf_sysutil_dupfd2(fd, STDIN_FILENO); vsf_sysutil_dupfd2(fd, STDOUT_FILENO); vsf_sysutil_dupfd2(fd, STDERR_FILENO); if ( fd > 2 ) { vsf_sysutil_close(fd); } return; error: die("reopening standard file descriptors to /dev/null failed"); } void vsf_sysutil_tzset(void) { int retval; char tzbuf[sizeof("+HHMM!")]; time_t the_time = time(NULL); struct tm* p_tm; tzset(); p_tm = localtime(&the_time); if (p_tm == NULL) { die("localtime"); } /* Set our timezone in the TZ environment variable to cater for the fact * that modern glibc does not cache /etc/localtime (which becomes inaccessible * when we chroot(). */ retval = strftime(tzbuf, sizeof(tzbuf), "%z", p_tm); tzbuf[sizeof(tzbuf) - 1] = '\0'; if (retval == 5) { /* Static because putenv() does not copy the string. */ static char envtz[sizeof("TZ=UTC-hh:mm")]; /* Insert a colon so we have e.g. -05:00 instead of -0500 */ tzbuf[5] = tzbuf[4]; tzbuf[4] = tzbuf[3]; tzbuf[3] = ':'; /* Invert the sign - we just got the offset _from_ UTC but for TZ, we need * the offset _to_ UTC. */ if (tzbuf[0] == '+') { tzbuf[0] = '-'; } else { tzbuf[0] = '+'; } snprintf(envtz, sizeof(envtz), "TZ=UTC%s", tzbuf); putenv(envtz); s_timezone = ((tzbuf[1] - '0') * 10 + (tzbuf[2] - '0')) * 60 * 60; s_timezone += ((tzbuf[4] - '0') * 10 + (tzbuf[5] - '0')) * 60; if (tzbuf[0] == '-') { s_timezone *= -1; } } /* Call in to the time subsystem again now that TZ is set, trying to force * caching of the actual zoneinfo for the timezone. */ p_tm = localtime(&the_time); if (p_tm == NULL) { die("localtime #2"); } p_tm = gmtime(&the_time); if (p_tm == NULL) { die("gmtime"); } } const char* vsf_sysutil_get_current_date(void) { static char datebuf[64]; time_t curr_time; const struct tm* p_tm; int i = 0; curr_time = vsf_sysutil_get_time_sec(); p_tm = localtime(&curr_time); if (strftime(datebuf, sizeof(datebuf), "%a %b!%d %H:%M:%S %Y", p_tm) == 0) { die("strftime"); } datebuf[sizeof(datebuf) - 1] = '\0'; /* This hack is because %e in strftime() isn't so portable */ while (datebuf[i] != '!' && datebuf[i] != '\0') { ++i; } if (datebuf[i] == '!') { datebuf[i] = ' '; if (datebuf[i+1] == '0') { datebuf[i+1] = ' '; } } return datebuf; } long vsf_sysutil_get_time_sec(void) { if (gettimeofday(&s_current_time, NULL) != 0) { die("gettimeofday"); } return s_current_time.tv_sec; } long vsf_sysutil_get_time_usec(void) { return s_current_time.tv_usec; } void vsf_sysutil_qsort(void* p_base, unsigned int num_elem, unsigned int elem_size, int (*p_compar)(const void *, const void *)) { qsort(p_base, num_elem, elem_size, p_compar); } void vsf_sysutil_sleep(double seconds) { int retval; int saved_errno; double fractional; time_t secs; struct timespec ts; secs = (time_t) seconds; fractional = seconds - (double) secs; ts.tv_sec = secs; ts.tv_nsec = (long) (fractional * (double) 1000000000); do { retval = nanosleep(&ts, &ts); saved_errno = errno; vsf_sysutil_check_pending_actions(kVSFSysUtilUnknown, 0, 0); } while (retval == -1 && saved_errno == EINTR); } char* vsf_sysutil_getenv(const char* p_var) { return getenv(p_var); } void vsf_sysutil_openlog(int force) { int facility = LOG_DAEMON; int option = LOG_PID; if (!force) { option |= LOG_NDELAY; } #ifdef LOG_FTP facility = LOG_FTP; #endif openlog("vsftpd", option, facility); } void vsf_sysutil_closelog(void) { closelog(); } void vsf_sysutil_syslog(const char* p_text, int severe) { int prio = LOG_INFO; if (severe) { prio = LOG_WARNING; } syslog(prio, "%s", p_text); } long vsf_sysutil_parse_time(const char* p_text) { struct tm the_time; unsigned int len = vsf_sysutil_strlen(p_text); vsf_sysutil_memclr(&the_time, sizeof(the_time)); if (len >= 8) { char yr[5]; char mon[3]; char day[3]; vsf_sysutil_strcpy(yr, p_text, 5); vsf_sysutil_strcpy(mon, p_text + 4, 3); vsf_sysutil_strcpy(day, p_text + 6, 3); the_time.tm_year = vsf_sysutil_atoi(yr) - 1900; the_time.tm_mon = vsf_sysutil_atoi(mon) - 1; the_time.tm_mday = vsf_sysutil_atoi(day); } if (len >= 14) { char hr[3]; char mins[3]; char sec[3]; vsf_sysutil_strcpy(hr, p_text + 8, 3); vsf_sysutil_strcpy(mins, p_text + 10, 3); vsf_sysutil_strcpy(sec, p_text + 12, 3); the_time.tm_hour = vsf_sysutil_atoi(hr); the_time.tm_min = vsf_sysutil_atoi(mins); the_time.tm_sec = vsf_sysutil_atoi(sec); } return mktime(&the_time); } int vsf_sysutil_setmodtime(const char* p_file, long the_time, int is_localtime) { struct utimbuf new_times; if (!is_localtime) { the_time -= s_timezone; } vsf_sysutil_memclr(&new_times, sizeof(new_times)); new_times.actime = the_time; new_times.modtime = the_time; return utime(p_file, &new_times); } void vsf_sysutil_ftruncate(int fd) { int ret = ftruncate(fd, 0); if (ret != 0) { die("ftruncate"); } } int vsf_sysutil_getuid(void) { return getuid(); } void vsf_sysutil_set_address_space_limit(unsigned long bytes) { /* Unfortunately, OpenBSD is missing RLIMIT_AS. */ #ifdef RLIMIT_AS int ret; struct rlimit rlim; rlim.rlim_cur = bytes; rlim.rlim_max = bytes; ret = setrlimit(RLIMIT_AS, &rlim); /* Permit EPERM as this could indicate that the shell launching vsftpd already * has a lower limit. */ if (ret != 0 && errno != EPERM) { die("setrlimit"); } #endif /* RLIMIT_AS */ (void) bytes; } void vsf_sysutil_set_no_fds() { int ret; struct rlimit rlim; rlim.rlim_cur = 0; rlim.rlim_max = 0; ret = setrlimit(RLIMIT_NOFILE, &rlim); if (ret != 0) { die("setrlimit NOFILE"); } } void vsf_sysutil_set_no_procs() { #ifdef RLIMIT_NPROC int ret; struct rlimit rlim; rlim.rlim_cur = 0; rlim.rlim_max = 0; ret = setrlimit(RLIMIT_NPROC, &rlim); if (ret != 0) { die("setrlimit NPROC"); } #endif } void vsf_sysutil_post_fork() { int i; /* Don't inherit any exit function. */ s_exit_func = NULL; /* Uncached the current PID. */ s_current_pid = -1; /* Don't inherit anything relating to the synchronous signal system */ s_io_handler = NULL; for (i=0; i < NSIG; ++i) { s_sig_details[i].sync_sig_handler = NULL; } for (i=0; i < NSIG; ++i) { s_sig_details[i].pending = 0; } } vsftpd-3.0.3/features.c0000644000175000017500000000227511672143554014025 0ustar chrischris/* * Part of Very Secure FTPd * Licence: GPL v2 * Author: Chris Evans * features.c * * Routines to tell the client what features we support. */ #include "features.h" #include "ftpcodes.h" #include "ftpcmdio.h" #include "tunables.h" void handle_feat(struct vsf_session* p_sess) { vsf_cmdio_write_hyphen(p_sess, FTP_FEAT, "Features:"); if (tunable_ssl_enable) { if (tunable_sslv2 || tunable_sslv3) { vsf_cmdio_write_raw(p_sess, " AUTH SSL\r\n"); } if (tunable_tlsv1) { vsf_cmdio_write_raw(p_sess, " AUTH TLS\r\n"); } } if (tunable_port_enable) { vsf_cmdio_write_raw(p_sess, " EPRT\r\n"); } if (tunable_pasv_enable) { vsf_cmdio_write_raw(p_sess, " EPSV\r\n"); } vsf_cmdio_write_raw(p_sess, " MDTM\r\n"); if (tunable_pasv_enable) { vsf_cmdio_write_raw(p_sess, " PASV\r\n"); } if (tunable_ssl_enable) { vsf_cmdio_write_raw(p_sess, " PBSZ\r\n"); vsf_cmdio_write_raw(p_sess, " PROT\r\n"); } vsf_cmdio_write_raw(p_sess, " REST STREAM\r\n"); vsf_cmdio_write_raw(p_sess, " SIZE\r\n"); vsf_cmdio_write_raw(p_sess, " TVFS\r\n"); vsf_cmdio_write_raw(p_sess, " UTF8\r\n"); vsf_cmdio_write(p_sess, FTP_FEAT, "End"); } vsftpd-3.0.3/SPEED0000644000175000017500000000222410750743677012630 0ustar chrischris- See also BENCHMARKS This FTPd should be very performant. The reasons for this are below, followed by specific benchmarks as and when I get them. 1) Generally, it is a fairly minimal FTPd. There should not be much code and/or syscall bloat. 2) For binary downloads, Linux sendfile() is used. This is a lot lighter on CPU/syscall usage than your regular read()/write() loop. 3) The "ls" command is fully internal. That is to say, an external "ls" command does not need to be launch. Launching an external process is costly because of the fork(), exec(), ELF loader startup, etc. It is not all good news, of course. Potential sources of poor performance include 1) Overhead of two processes per session (in some common configurations). 2) Excessive heap usage hidden behind the string API. BENCHMARKS ========== 1) vsftpd downloads ASCII data at at least twice the rate of wu-ftpd. 2) vsftpd has achieved 86Mbyte/sec download over Gigabit ethernet between Linux-2.4.x boxes (thanks to sendfile()) 3) vsftpd has smaller virtual memory usage (and RSS, it seems) 4) Various reports have trickled in and indicate that vsftpd thumps wu-ftpd in performance tests. vsftpd-3.0.3/sslslave.h0000644000175000017500000000066311120276354014040 0ustar chrischris#ifndef VSF_SSLSLAVE_H #define VSF_SSLSLAVE_H struct vsf_session; /* ssl_slave() * PURPOSE * An internal function that takes care of running the "SSL slave" process. It * is needed because the initial SSL handshake state may belong to a different * process that the process running the FTP protocol. * PARAMETERS * p_sess - the session object */ void ssl_slave(struct vsf_session* p_sess); #endif /* VSF_SSLSLAVE_H */ vsftpd-3.0.3/README.ssl0000644000175000017500000000410310750743700013506 0ustar chrischrisAs of vsftpd version 2.0.0, SSL / TLS support is provided. The SSL / TLS support provides the ability to encrypt FTP logins and subsequent commands, as well as the data transfers themselves. The encyption will, for example, stop the stealing of sensitive passwords via network snooping. By default, SSL support is disabled both at compile time and at runtime. Before considering enabling / using SSL support, there are some security considerations: - Only enable SSL if absolutely necessary. Enabling SSL will allow attackers to make use of any security problems in the OpenSSL libraries. Note that the OpenSSL libraries are a large quantity of code and have had the occasional security problem in the past. For example, your server might use virtual users to control access to non-sensitive download content. In this case, the passwords might not be worth securing with SSL. - After enabling SSL, consider restricting access to an SSL enabled server where feasible. For example, only the internal network might need access. In order to enable and use SSL support, you need the following: - vsftpd built with OpenSSL support. This is a decision your vsftpd packager made, or if you are building vsftpd yourself, edit "builddefs.h" and change the "#undef VSF_BUILD_SSL" to "#define VSF_BUILD_SSL". - "ssl_enable=YES" in your vsftpd.conf. - A SSL certificate. By default, an RSA certificate is looked for at the location /usr/share/ssl/certs/vsftpd.pem. To get an RSA certificate, either buy one from a certificate authority, or you can create your own self-signed certificate. If you have OpenSSL installed, you may find a "Makefile" in your shared certificates directory, e.g. /usr/share/ssl/certs. In that case, go to that directory and type e.g. "make vsftpd.pem". Then answer the questions you are asked. Alternatively, read the man page for "openssl". - Also be aware of the following SSL related parameters. Read the vsftpd.conf.5 manual page to learn about them: allow_anon_ssl, force_local_logins_ssl, force_local_data_ssl, ssl_sslv2, ssl_sslv3, ssl_tlsv1, rsa_cert_file, dsa_cert_file, ssl_ciphers. vsftpd-3.0.3/defs.h0000644000175000017500000000137712553277065013143 0ustar chrischris#ifndef VSF_DEFS_H #define VSF_DEFS_H #define VSFTP_DEFAULT_CONFIG "/etc/vsftpd.conf" #define VSFTP_COMMAND_FD 0 #define VSFTP_PASSWORD_MAX 128 #define VSFTP_USERNAME_MAX 128 #define VSFTP_MAX_COMMAND_LINE 4096 #define VSFTP_DATA_BUFSIZE 65536 #define VSFTP_DIR_BUFSIZE 16384 #define VSFTP_MATCHITERS_MAX 1000 #define VSFTP_PATH_MAX 4096 #define VSFTP_CONF_FILE_MAX 100000 #define VSFTP_LISTEN_BACKLOG 32 #define VSFTP_SECURE_UMASK 077 #define VSFTP_ROOT_UID 0 /* Must be at least the size of VSFTP_MAX_COMMAND_LINE, VSFTP_DIR_BUFSIZE and VSFTP_DATA_BUFSIZE*2 */ #define VSFTP_PRIVSOCK_MAXSTR VSFTP_DATA_BUFSIZE * 2 #define VSFTP_AS_LIMIT 200UL * 1024 * 1024 #endif /* VSF_DEFS_H */ vsftpd-3.0.3/filestr.h0000644000175000017500000000150210750743701013650 0ustar chrischris#ifndef VSF_FILESTR_H #define VSF_FILESTR_H /* Forward declares */ struct mystr; /* str_fileread() * PURPOSE * Read the contents of a file into a string buffer, up to a size limit of * "maxsize" * PARAMETERS * p_str - destination buffer object to contain the file * p_filename - the filename to try and read into the buffer * maxsize - the maximum amount of buffer we will fill. Larger files will * be truncated. * RETURNS * An integer representing the success/failure of opening the file * "p_filename". Zero indicates success. If successful, the file is read into * the "p_str" string object. If not successful, "p_str" will point to an * empty buffer. */ int str_fileread(struct mystr* p_str, const char* p_filename, unsigned int maxsize); #endif /* VSF_FILESTR_H */ vsftpd-3.0.3/postprivparent.h0000644000175000017500000000061510750743677015320 0ustar chrischris#ifndef VSF_LOGINPRIVPARENT_H #define VSF_LOGINPRIVPARENT_H struct vsf_session; /* vsf_priv_parent_postlogin() * PURPOSE * Called in the two process security model to commence "listening" for * requests from the unprivileged child. * PARAMETERS * p_sess - the current session object */ void vsf_priv_parent_postlogin(struct vsf_session* p_sess); #endif /* VSF_LOGINPRIVPARENT_H */ vsftpd-3.0.3/netstr.h0000644000175000017500000000526511735424374013537 0ustar chrischris#ifndef VSFTP_NETSTR_H #define VSFTP_NETSTR_H struct mystr; struct vsf_session; typedef int (*str_netfd_read_t)(struct vsf_session* p_sess, char*, unsigned int); /* str_netfd_alloc() * PURPOSE * Read a string from a network socket into a string buffer object. The string * is delimited by a specified string terminator character. * If any network related errors occur trying to read the string, this call * will exit the program. * This method avoids reading one character at a time from the network. * PARAMETERS * p_sess - the session object, used for passing into the I/O callbacks * p_str - the destination string object * term - the character which will terminate the string. This character * is included in the returned string. * p_readbuf - pointer to a scratch buffer into which to read from the * network. This buffer must be at least "maxlen" characters! * maxlen - maximum length of string to return. If this limit is passed, * an empty string will be returned. * p_peekfunc - a function called to peek data from the network * p_readfunc - a function called to read data from the network * RETURNS * -1 upon reaching max buffer length without seeing terminator, or the number * of bytes read, _including_ the terminator. 0 for an EOF on the socket. * Does not return (exits) for a serious socket error. */ int str_netfd_alloc(struct vsf_session* p_sess, struct mystr* p_str, char term, char* p_readbuf, unsigned int maxlen, str_netfd_read_t p_peekfunc, str_netfd_read_t p_readfunc); /* str_netfd_read() * PURPOSE * Fills contents of a string buffer object from a (typically network) file * descriptor. * PARAMETERS * p_str - the string object to be filled * fd - the file descriptor to read from * len - the number of bytes to read * RETURNS * Number read on success, -1 on failure. The read is considered a failure * unless the full requested byte count is read. */ int str_netfd_read(struct mystr* p_str, int fd, unsigned int len); /* str_netfd_write() * PURPOSE * Write the contents of a string buffer object out to a (typically network) * file descriptor. * PARAMETERS * p_str - the string object to send * fd - the file descriptor to write to * RETURNS * Number written on success, -1 on failure. The write is considered a failure * unless the full string buffer object is written. */ int str_netfd_write(const struct mystr* p_str, int fd); #endif /* VSFTP_NETSTR_H */ vsftpd-3.0.3/netstr.c0000644000175000017500000000547511735426604013533 0ustar chrischris/* * Part of Very Secure FTPd * Licence: GPL v2 * Author: Chris Evans * netstr.c * * The netstr interface extends the standard string interface, adding * functions which can cope safely with building strings from the network, * and send them out too. */ #include "netstr.h" #include "str.h" #include "sysstr.h" #include "utility.h" #include "sysutil.h" int str_netfd_alloc(struct vsf_session* p_sess, struct mystr* p_str, char term, char* p_readbuf, unsigned int maxlen, str_netfd_read_t p_peekfunc, str_netfd_read_t p_readfunc) { int retval; unsigned int bytes_read; unsigned int i; char* p_readpos = p_readbuf; unsigned int left = maxlen; str_empty(p_str); while (1) { if (p_readpos + left != p_readbuf + maxlen) { bug("poor buffer accounting in str_netfd_alloc"); } /* Did we hit the max? */ if (left == 0) { return -1; } retval = (*p_peekfunc)(p_sess, p_readpos, left); if (vsf_sysutil_retval_is_error(retval)) { die("vsf_sysutil_recv_peek"); } else if (retval == 0) { return 0; } bytes_read = (unsigned int) retval; /* Search for the terminator */ for (i=0; i < bytes_read; i++) { if (p_readpos[i] == term) { /* Got it! */ i++; retval = (*p_readfunc)(p_sess, p_readpos, i); if (vsf_sysutil_retval_is_error(retval) || (unsigned int) retval != i) { die("vsf_sysutil_read_loop"); } if (p_readpos[i - 1] != term) { die("missing terminator in str_netfd_alloc"); } str_alloc_alt_term(p_str, p_readbuf, term); return (int) i; } } /* Not found in this read chunk, so consume the data and re-loop */ if (bytes_read > left) { bug("bytes_read > left in str_netfd_alloc"); } left -= bytes_read; retval = (*p_readfunc)(p_sess, p_readpos, bytes_read); if (vsf_sysutil_retval_is_error(retval) || (unsigned int) retval != bytes_read) { die("vsf_sysutil_read_loop"); } p_readpos += bytes_read; } /* END: while(1) */ } int str_netfd_write(const struct mystr* p_str, int fd) { int ret = 0; int retval; unsigned int str_len = str_getlen(p_str); if (str_len == 0) { bug("zero str_len in str_netfd_write"); } retval = str_write_loop(p_str, fd); if (vsf_sysutil_retval_is_error(retval) || (unsigned int) retval != str_len) { ret = -1; } return ret; } int str_netfd_read(struct mystr* p_str, int fd, unsigned int len) { int retval; str_reserve(p_str, len); str_trunc(p_str, len); retval = str_read_loop(p_str, fd); if (vsf_sysutil_retval_is_error(retval) || (unsigned int) retval != len) { return -1; } return retval; } vsftpd-3.0.3/secutil.c0000644000175000017500000000722711734500254013652 0ustar chrischris/* * Part of Very Secure FTPd * Licence: GPL v2 * Author: Chris Evans * secutil.c */ #include "secutil.h" #include "str.h" #include "sysutil.h" #include "sysstr.h" #include "utility.h" #include "sysdeputil.h" void vsf_secutil_change_credentials(const struct mystr* p_user_str, const struct mystr* p_dir_str, const struct mystr* p_ext_dir_str, unsigned int caps, unsigned int options) { struct vsf_sysutil_user* p_user; if (!vsf_sysutil_running_as_root()) { bug("vsf_secutil_change_credentials: not running as root"); } p_user = str_getpwnam(p_user_str); if (p_user == 0) { die2("cannot locate user entry:", str_getbuf(p_user_str)); } { struct mystr dir_str = INIT_MYSTR; /* Work out where the chroot() jail is */ if (p_dir_str == 0 || str_isempty(p_dir_str)) { str_alloc_text(&dir_str, vsf_sysutil_user_get_homedir(p_user)); } else { str_copy(&dir_str, p_dir_str); } /* Sort out supplementary groups before the chroot(). We need to access * /etc/groups */ if (options & VSF_SECUTIL_OPTION_USE_GROUPS) { vsf_sysutil_initgroups(p_user); } else { vsf_sysutil_clear_supp_groups(); } /* Always do the chdir() regardless of whether we are chroot()'ing */ { /* Do chdir() with the target effective IDs to cater for NFS mounted * home directories. */ int saved_euid = 0; int saved_egid = 0; int retval; if (options & VSF_SECUTIL_OPTION_CHANGE_EUID) { saved_euid = vsf_sysutil_geteuid(); saved_egid = vsf_sysutil_getegid(); vsf_sysutil_setegid(p_user); vsf_sysutil_seteuid(p_user); } retval = str_chdir(&dir_str); if (retval != 0) { die2("cannot change directory:", str_getbuf(&dir_str)); } if (p_ext_dir_str && !str_isempty(p_ext_dir_str)) { retval = str_chdir(p_ext_dir_str); /* Failure on the extra directory is OK as long as we're not in * chroot() mode */ if (retval != 0 && !(options & VSF_SECUTIL_OPTION_CHROOT)) { retval = 0; } } if (retval != 0) { die2("cannot change directory:", str_getbuf(p_ext_dir_str)); } if (options & VSF_SECUTIL_OPTION_CHANGE_EUID) { vsf_sysutil_seteuid_numeric(saved_euid); vsf_sysutil_setegid_numeric(saved_egid); } /* Do the chroot() if required */ if (options & VSF_SECUTIL_OPTION_CHROOT) { vsf_sysutil_chroot("."); } } str_free(&dir_str); } if (options & VSF_SECUTIL_OPTION_NO_FDS) { vsf_sysutil_set_no_fds(); } /* Handle capabilities */ if (caps) { if (!vsf_sysdep_has_capabilities()) { /* Need privilege but OS has no capabilities - have to keep root */ return; } if (!vsf_sysdep_has_capabilities_as_non_root()) { vsf_sysdep_adopt_capabilities(caps); return; } vsf_sysdep_keep_capabilities(); } /* Set group id */ vsf_sysutil_setgid(p_user); /* Finally set user id */ vsf_sysutil_setuid(p_user); if (caps) { vsf_sysdep_adopt_capabilities(caps); } if (options & VSF_SECUTIL_OPTION_NO_PROCS) { vsf_sysutil_set_no_procs(); } /* Misconfiguration check: don't ever chroot() to a directory writable by * the current user. */ if ((options & VSF_SECUTIL_OPTION_CHROOT) && !(options & VSF_SECUTIL_OPTION_ALLOW_WRITEABLE_ROOT)) { if (vsf_sysutil_write_access("/")) { die("vsftpd: refusing to run with writable root inside chroot()"); } } } vsftpd-3.0.3/ascii.c0000644000175000017500000000373711117367374013305 0ustar chrischris/* * Part of Very Secure FTPd * Licence: GPL v2 * Author: Chris Evans * ascii.c * * Routines to handle ASCII mode tranfers. Yuk. */ #include "ascii.h" struct ascii_to_bin_ret vsf_ascii_ascii_to_bin(char* p_buf, unsigned int in_len, int prev_cr) { /* Task: translate all \r\n into plain \n. A plain \r not followed by \n must * not be removed. */ struct ascii_to_bin_ret ret; unsigned int indexx = 0; unsigned int written = 0; char* p_out = p_buf + 1; ret.last_was_cr = 0; if (prev_cr && (!in_len || p_out[0] != '\n')) { p_buf[0] = '\r'; ret.p_buf = p_buf; written++; } else { ret.p_buf = p_out; } while (indexx < in_len) { char the_char = p_buf[indexx + 1]; if (the_char != '\r') { *p_out++ = the_char; written++; } else if (indexx == in_len - 1) { ret.last_was_cr = 1; } else if (p_buf[indexx + 2] != '\n') { *p_out++ = the_char; written++; } indexx++; } ret.stored = written; return ret; } struct bin_to_ascii_ret vsf_ascii_bin_to_ascii(const char* p_in, char* p_out, unsigned int in_len, int prev_cr) { /* Task: translate all \n not preceeded by \r into \r\n. * Note that \r\n stays as \r\n. We used to map it to \r\r\n like wu-ftpd * but have switched to leaving it, like the more popular proftpd. */ struct bin_to_ascii_ret ret = { 0, 0 }; unsigned int indexx = 0; unsigned int written = 0; char last_char = 0; if (prev_cr) { last_char = '\r'; ret.last_was_cr = 1; } while (indexx < in_len) { char the_char = p_in[indexx]; if (the_char == '\n' && last_char != '\r') { *p_out++ = '\r'; written++; } *p_out++ = the_char; written++; indexx++; last_char = the_char; if (the_char == '\r') { ret.last_was_cr = 1; } else { ret.last_was_cr = 0; } } ret.stored = written; return ret; } vsftpd-3.0.3/sysstr.h0000644000175000017500000000266711433071031013551 0ustar chrischris#ifndef VSF_SYSSTR_H #define VSF_SYSSTR_H /* Forward declarations */ struct mystr; struct vsf_sysutil_statbuf; struct vsf_sysutil_dir; struct vsf_sysutil_user; void str_getcwd(struct mystr* p_str); int str_readlink(struct mystr* p_str, const struct mystr* p_filename_str); int str_write_loop(const struct mystr* p_str, const int fd); int str_read_loop(struct mystr* p_str, const int fd); int str_mkdir(const struct mystr* p_str, const unsigned int mode); int str_rmdir(const struct mystr* p_str); int str_unlink(const struct mystr* p_str); int str_chdir(const struct mystr* p_str); enum EVSFSysStrOpenMode { kVSFSysStrOpenUnknown = 0, kVSFSysStrOpenReadOnly = 1 }; int str_open(const struct mystr* p_str, const enum EVSFSysStrOpenMode mode); int str_create(const struct mystr* p_str); int str_create_exclusive(const struct mystr* p_str); int str_chmod(const struct mystr* p_str, unsigned int mode); int str_stat(const struct mystr* p_str, struct vsf_sysutil_statbuf** p_ptr); int str_lstat(const struct mystr* p_str, struct vsf_sysutil_statbuf** p_ptr); int str_rename(const struct mystr* p_from_str, const struct mystr* p_to_str); struct vsf_sysutil_dir* str_opendir(const struct mystr* p_str); void str_next_dirent(struct mystr* p_filename_str, struct vsf_sysutil_dir* p_dir); struct vsf_sysutil_user* str_getpwnam(const struct mystr* p_user_str); void str_syslog(const struct mystr* p_str, int severe); #endif /* VSF_SYSSTR_H */ vsftpd-3.0.3/strlist.h0000644000175000017500000000160112025267604013704 0ustar chrischris#ifndef VSF_STRLIST_H #define VSF_STRLIST_H /* Forward declarations */ struct mystr; struct mystr_list_node; struct mystr_list { unsigned int PRIVATE_HANDS_OFF_alloc_len; unsigned int PRIVATE_HANDS_OFF_list_len; struct mystr_list_node* PRIVATE_HANDS_OFF_p_nodes; }; #define INIT_STRLIST \ { 0, 0, (void*)0 } void str_list_free(struct mystr_list* p_list); void str_list_add(struct mystr_list* p_list, const struct mystr* p_str, const struct mystr* p_sort_key_str); void str_list_sort(struct mystr_list* p_list, int reverse); unsigned int str_list_get_length(const struct mystr_list* p_list); int str_list_contains_str(const struct mystr_list* p_list, const struct mystr* p_str); const struct mystr* str_list_get_pstr(const struct mystr_list* p_list, unsigned int indexx); #endif /* VSF_STRLIST_H */ vsftpd-3.0.3/Makefile0000644000175000017500000000321012025277547013474 0ustar chrischris# Makefile for systems with GNU tools CC = gcc INSTALL = install IFLAGS = -idirafter dummyinc #CFLAGS = -g CFLAGS = -O2 -fPIE -fstack-protector --param=ssp-buffer-size=4 \ -Wall -W -Wshadow -Werror -Wformat-security \ -D_FORTIFY_SOURCE=2 \ #-pedantic -Wconversion LIBS = `./vsf_findlibs.sh` LINK = -Wl,-s LDFLAGS = -fPIE -pie -Wl,-z,relro -Wl,-z,now OBJS = main.o utility.o prelogin.o ftpcmdio.o postlogin.o privsock.o \ tunables.o ftpdataio.o secbuf.o ls.o \ postprivparent.o logging.o str.o netstr.o sysstr.o strlist.o \ banner.o filestr.o parseconf.o secutil.o \ ascii.o oneprocess.o twoprocess.o privops.o standalone.o hash.o \ tcpwrap.o ipaddrparse.o access.o features.o readwrite.o opts.o \ ssl.o sslslave.o ptracesandbox.o ftppolicy.o sysutil.o sysdeputil.o \ seccompsandbox.o .c.o: $(CC) -c $*.c $(CFLAGS) $(IFLAGS) vsftpd: $(OBJS) $(CC) -o vsftpd $(OBJS) $(LINK) $(LDFLAGS) $(LIBS) install: if [ -x /usr/local/sbin ]; then \ $(INSTALL) -m 755 vsftpd /usr/local/sbin/vsftpd; \ else \ $(INSTALL) -m 755 vsftpd /usr/sbin/vsftpd; fi if [ -x /usr/local/man ]; then \ $(INSTALL) -m 644 vsftpd.8 /usr/local/man/man8/vsftpd.8; \ $(INSTALL) -m 644 vsftpd.conf.5 /usr/local/man/man5/vsftpd.conf.5; \ elif [ -x /usr/share/man ]; then \ $(INSTALL) -m 644 vsftpd.8 /usr/share/man/man8/vsftpd.8; \ $(INSTALL) -m 644 vsftpd.conf.5 /usr/share/man/man5/vsftpd.conf.5; \ else \ $(INSTALL) -m 644 vsftpd.8 /usr/man/man8/vsftpd.8; \ $(INSTALL) -m 644 vsftpd.conf.5 /usr/man/man5/vsftpd.conf.5; fi if [ -x /etc/xinetd.d ]; then \ $(INSTALL) -m 644 xinetd.d/vsftpd /etc/xinetd.d/vsftpd; fi clean: rm -f *.o *.swp vsftpd vsftpd-3.0.3/banner.c0000644000175000017500000000411612025251051013431 0ustar chrischris/* * Part of Very Secure FTPd * Licence: GPL v2 * Author: Chris Evans * banner.c * * Calls exposed to handle the junk a typical FTP server has to do upon * entering a new directory (messages, etc), as well as general banner * writing support. */ #include "banner.h" #include "strlist.h" #include "str.h" #include "sysstr.h" #include "tunables.h" #include "ftpcmdio.h" #include "filestr.h" #include "session.h" #include "sysutil.h" /* Definitions */ #define VSFTP_MAX_VISIT_REMEMBER 100 #define VSFTP_MAX_MSGFILE_SIZE 4000 void vsf_banner_dir_changed(struct vsf_session* p_sess, int ftpcode) { struct mystr dir_str = INIT_MYSTR; /* Do nothing if .message support is off */ if (!tunable_dirmessage_enable) { return; } if (p_sess->p_visited_dir_list == 0) { struct mystr_list the_list = INIT_STRLIST; p_sess->p_visited_dir_list = vsf_sysutil_malloc(sizeof(struct mystr_list)); *p_sess->p_visited_dir_list = the_list; } str_getcwd(&dir_str); /* Do nothing if we already visited this directory */ if (!str_list_contains_str(p_sess->p_visited_dir_list, &dir_str)) { /* Just in case, cap the max. no of visited directories we'll remember */ if (str_list_get_length(p_sess->p_visited_dir_list) < VSFTP_MAX_VISIT_REMEMBER) { str_list_add(p_sess->p_visited_dir_list, &dir_str, 0); } /* If we have a .message file, squirt it out prepended by the ftpcode and * the continuation mark '-' */ { struct mystr msg_file_str = INIT_MYSTR; if (tunable_message_file) { (void) str_fileread(&msg_file_str, tunable_message_file, VSFTP_MAX_MSGFILE_SIZE); } vsf_banner_write(p_sess, &msg_file_str, ftpcode); str_free(&msg_file_str); } } str_free(&dir_str); } void vsf_banner_write(struct vsf_session* p_sess, struct mystr* p_str, int ftpcode) { struct mystr msg_line_str = INIT_MYSTR; unsigned int str_pos = 0; while (str_getline(p_str, &msg_line_str, &str_pos)) { vsf_cmdio_write_str_hyphen(p_sess, ftpcode, &msg_line_str); } str_free(&msg_line_str); } vsftpd-3.0.3/session.h0000644000175000017500000000431411737172367013701 0ustar chrischris#ifndef VSF_SESSION_H #define VSF_SESSION_H #ifndef VSFTP_STR_H #include "str.h" #endif #ifndef VSF_FILESIZE_H #include "filesize.h" #endif struct vsf_sysutil_sockaddr; struct mystr_list; /* This struct contains variables specific to the state of the current FTP * session */ struct vsf_session { /* Details of the control connection */ struct vsf_sysutil_sockaddr* p_local_addr; struct vsf_sysutil_sockaddr* p_remote_addr; char* p_control_line_buf; int idle_timeout; int data_timeout; /* Details of the data connection */ int pasv_listen_fd; struct vsf_sysutil_sockaddr* p_port_sockaddr; int data_fd; int data_progress; unsigned int bw_rate_max; long bw_send_start_sec; long bw_send_start_usec; /* Details of the login */ int is_anonymous; int is_guest; struct mystr user_str; struct mystr anon_pass_str; /* Details of the FTP protocol state */ filesize_t restart_pos; int is_ascii; struct mystr rnfr_filename_str; int abor_received; int epsv_all; /* HTTP hacks */ int is_http; struct mystr http_get_arg; /* Details of FTP session state */ struct mystr_list* p_visited_dir_list; /* Details of userids which are interesting to us */ int anon_ftp_uid; int guest_user_uid; int anon_upload_chown_uid; /* Things we need to cache before we chroot() */ struct mystr banned_email_str; struct mystr email_passwords_str; struct mystr userlist_str; struct mystr banner_str; int tcp_wrapper_ok; /* Logging related details */ int xferlog_fd; int vsftpd_log_fd; struct mystr remote_ip_str; unsigned long log_type; long log_start_sec; long log_start_usec; struct mystr log_str; filesize_t transfer_size; /* Buffers */ struct mystr ftp_cmd_str; struct mystr ftp_arg_str; /* Parent<->child comms channel */ int parent_fd; int child_fd; /* Other details */ unsigned int num_clients; unsigned int num_this_ip; struct mystr home_str; /* Secure connections state */ int control_use_ssl; int data_use_ssl; void* p_ssl_ctx; void* p_control_ssl; void* p_data_ssl; struct mystr control_cert_digest; int ssl_slave_active; int ssl_slave_fd; int ssl_consumer_fd; unsigned int login_fails; }; #endif /* VSF_SESSION_H */ vsftpd-3.0.3/ascii.h0000644000175000017500000000375211117364033013274 0ustar chrischris#ifndef VSFTP_ASCII_H #define VSFTP_ASCII_H struct mystr; /* vsf_ascii_ascii_to_bin() * PURPOSE * This function converts an input buffer from ascii format to binary format. * This entails ripping out all occurences of '\r' that are followed by '\n'. * The result is stored in "p_out". * PARAMETERS * p_in - the input and output buffer, which MUST BE at least as big as * "in_len" PLUS ONE. This is to cater for a leading '\r' in the * buffer if certain conditions are met. * in_len - the length in bytes of the buffer. * prev_cr - set to non-zero if this buffer fragment was immediately * preceeded by a '\r'. * RETURNS * The number of characters stored in the buffer, the buffer address, and * if we ended on a '\r'. */ struct ascii_to_bin_ret { unsigned int stored; int last_was_cr; char* p_buf; }; struct ascii_to_bin_ret vsf_ascii_ascii_to_bin( char* p_in, unsigned int in_len, int prev_cr); /* vsf_ascii_bin_to_ascii() * PURPOSE * This function converts an input buffer from binary format to ascii format. * This entails replacing all occurences of '\n' with '\r\n'. The result is * stored in "p_out". * PARAMETERS * p_in - the input buffer, which is not modified * p_out - the output buffer, which MUST BE at least TWICE as big as * "in_len" * in_len - the length in bytes of the input buffer * prev_cr - set to non-zero if this buffer fragment was immediately * preceeded by a '\r'. * RETURNS * The number of characters stored in the output buffer, and whether the last * character stored was '\r'. */ struct bin_to_ascii_ret { unsigned int stored; int last_was_cr; }; struct bin_to_ascii_ret vsf_ascii_bin_to_ascii(const char* p_in, char* p_out, unsigned int in_len, int prev_cr); #endif /* VSFTP_ASCII_H */ vsftpd-3.0.3/standalone.c0000644000175000017500000001725012025277414014332 0ustar chrischris/* * Part of Very Secure FTPd * Licence: GPL v2 * Author: Chris Evans * standalone.c * * Code to listen on the network and launch children servants. */ #include "standalone.h" #include "parseconf.h" #include "tunables.h" #include "sysutil.h" #include "sysdeputil.h" #include "utility.h" #include "defs.h" #include "hash.h" #include "str.h" #include "ipaddrparse.h" static unsigned int s_children; static struct hash* s_p_ip_count_hash; static struct hash* s_p_pid_ip_hash; static unsigned int s_ipaddr_size; static void handle_sigchld(void* duff); static void handle_sighup(void* duff); static void prepare_child(int sockfd); static unsigned int handle_ip_count(void* p_raw_addr); static void drop_ip_count(void* p_raw_addr); static unsigned int hash_ip(unsigned int buckets, void* p_key); static unsigned int hash_pid(unsigned int buckets, void* p_key); struct vsf_client_launch vsf_standalone_main(void) { struct vsf_sysutil_sockaddr* p_accept_addr = 0; int listen_sock = -1; int retval; s_ipaddr_size = vsf_sysutil_get_ipaddr_size(); if (tunable_listen && tunable_listen_ipv6) { die("run two copies of vsftpd for IPv4 and IPv6"); } if (tunable_background) { int forkret = vsf_sysutil_fork(); if (forkret > 0) { /* Parent, just exit */ vsf_sysutil_exit(0); } /* Son, close standard FDs to avoid SSH hang-on-exit */ vsf_sysutil_reopen_standard_fds(); vsf_sysutil_make_session_leader(); } if (tunable_listen) { listen_sock = vsf_sysutil_get_ipv4_sock(); } else { listen_sock = vsf_sysutil_get_ipv6_sock(); } vsf_sysutil_activate_reuseaddr(listen_sock); s_p_ip_count_hash = hash_alloc(256, s_ipaddr_size, sizeof(unsigned int), hash_ip); s_p_pid_ip_hash = hash_alloc(256, sizeof(int), s_ipaddr_size, hash_pid); if (tunable_setproctitle_enable) { vsf_sysutil_setproctitle("LISTENER"); } vsf_sysutil_install_sighandler(kVSFSysUtilSigCHLD, handle_sigchld, 0, 1); vsf_sysutil_install_sighandler(kVSFSysUtilSigHUP, handle_sighup, 0, 1); if (tunable_listen) { struct vsf_sysutil_sockaddr* p_sockaddr = 0; vsf_sysutil_sockaddr_alloc_ipv4(&p_sockaddr); vsf_sysutil_sockaddr_set_port(p_sockaddr, (unsigned short) tunable_listen_port); if (!tunable_listen_address) { vsf_sysutil_sockaddr_set_any(p_sockaddr); } else { if (!vsf_sysutil_inet_aton(tunable_listen_address, p_sockaddr)) { die2("bad listen_address: ", tunable_listen_address); } } retval = vsf_sysutil_bind(listen_sock, p_sockaddr); vsf_sysutil_free(p_sockaddr); if (vsf_sysutil_retval_is_error(retval)) { die("could not bind listening IPv4 socket"); } } else { struct vsf_sysutil_sockaddr* p_sockaddr = 0; vsf_sysutil_sockaddr_alloc_ipv6(&p_sockaddr); vsf_sysutil_sockaddr_set_port(p_sockaddr, (unsigned short) tunable_listen_port); if (!tunable_listen_address6) { vsf_sysutil_sockaddr_set_any(p_sockaddr); } else { struct mystr addr_str = INIT_MYSTR; const unsigned char* p_raw_addr; str_alloc_text(&addr_str, tunable_listen_address6); p_raw_addr = vsf_sysutil_parse_ipv6(&addr_str); str_free(&addr_str); if (!p_raw_addr) { die2("bad listen_address6: ", tunable_listen_address6); } vsf_sysutil_sockaddr_set_ipv6addr(p_sockaddr, p_raw_addr); } retval = vsf_sysutil_bind(listen_sock, p_sockaddr); vsf_sysutil_free(p_sockaddr); if (vsf_sysutil_retval_is_error(retval)) { die("could not bind listening IPv6 socket"); } } retval = vsf_sysutil_listen(listen_sock, VSFTP_LISTEN_BACKLOG); if (vsf_sysutil_retval_is_error(retval)) { die("could not listen"); } vsf_sysutil_sockaddr_alloc(&p_accept_addr); while (1) { struct vsf_client_launch child_info; void* p_raw_addr; int new_child; int new_client_sock; new_client_sock = vsf_sysutil_accept_timeout( listen_sock, p_accept_addr, 0); if (vsf_sysutil_retval_is_error(new_client_sock)) { continue; } ++s_children; child_info.num_children = s_children; child_info.num_this_ip = 0; p_raw_addr = vsf_sysutil_sockaddr_get_raw_addr(p_accept_addr); child_info.num_this_ip = handle_ip_count(p_raw_addr); if (tunable_isolate) { if (tunable_http_enable && tunable_isolate_network) { new_child = vsf_sysutil_fork_isolate_all_failok(); } else { new_child = vsf_sysutil_fork_isolate_failok(); } } else { new_child = vsf_sysutil_fork_failok(); } if (new_child != 0) { /* Parent context */ vsf_sysutil_close(new_client_sock); if (new_child > 0) { hash_add_entry(s_p_pid_ip_hash, (void*)&new_child, p_raw_addr); } else { /* fork() failed, clear up! */ --s_children; drop_ip_count(p_raw_addr); } /* Fall through to while() loop and accept() again */ } else { /* Child context */ vsf_set_die_if_parent_dies(); vsf_sysutil_close(listen_sock); prepare_child(new_client_sock); /* By returning here we "launch" the child process with the same * contract as xinetd would provide. */ return child_info; } } } static void prepare_child(int new_client_sock) { /* We must satisfy the contract: command socket on fd 0, 1, 2 */ vsf_sysutil_dupfd2(new_client_sock, 0); vsf_sysutil_dupfd2(new_client_sock, 1); vsf_sysutil_dupfd2(new_client_sock, 2); if (new_client_sock > 2) { vsf_sysutil_close(new_client_sock); } } static void drop_ip_count(void* p_raw_addr) { unsigned int count; unsigned int* p_count = (unsigned int*)hash_lookup_entry(s_p_ip_count_hash, p_raw_addr); if (!p_count) { bug("IP address missing from hash"); } count = *p_count; if (!count) { bug("zero count for IP address"); } count--; *p_count = count; if (!count) { hash_free_entry(s_p_ip_count_hash, p_raw_addr); } } static void handle_sigchld(void* duff) { unsigned int reap_one = 1; (void) duff; while (reap_one) { reap_one = (unsigned int)vsf_sysutil_wait_reap_one(); if (reap_one) { struct vsf_sysutil_ipaddr* p_ip; /* Account total number of instances */ --s_children; /* Account per-IP limit */ p_ip = (struct vsf_sysutil_ipaddr*) hash_lookup_entry(s_p_pid_ip_hash, (void*)&reap_one); drop_ip_count(p_ip); hash_free_entry(s_p_pid_ip_hash, (void*)&reap_one); } } } static void handle_sighup(void* duff) { (void) duff; /* We don't crash the out the listener if an invalid config was added */ tunables_load_defaults(); vsf_parseconf_load_file(0, 0); } static unsigned int hash_ip(unsigned int buckets, void* p_key) { const unsigned char* p_raw_ip = (const unsigned char*)p_key; unsigned int val = 0; int shift = 24; unsigned int i; for (i = 0; i < s_ipaddr_size; ++i) { val = val ^ (unsigned int) (p_raw_ip[i] << shift); shift -= 8; if (shift < 0) { shift = 24; } } return val % buckets; } static unsigned int hash_pid(unsigned int buckets, void* p_key) { unsigned int* p_pid = (unsigned int*)p_key; return (*p_pid) % buckets; } static unsigned int handle_ip_count(void* p_ipaddr) { unsigned int* p_count = (unsigned int*)hash_lookup_entry(s_p_ip_count_hash, p_ipaddr); unsigned int count; if (!p_count) { count = 1; hash_add_entry(s_p_ip_count_hash, p_ipaddr, (void*)&count); } else { count = *p_count; count++; *p_count = count; } return count; } vsftpd-3.0.3/strlist.c0000644000175000017500000000762112025275503013704 0ustar chrischris/* * Part of Very Secure FTPd * Licence: GPL v2 * Author: Chris Evans * strlist.c */ /* Anti-lamer measures deployed, sir! */ #define PRIVATE_HANDS_OFF_alloc_len alloc_len #define PRIVATE_HANDS_OFF_list_len list_len #define PRIVATE_HANDS_OFF_p_nodes p_nodes #include "strlist.h" #include "str.h" #include "utility.h" #include "sysutil.h" struct mystr_list_node { struct mystr str; struct mystr sort_key_str; }; /* File locals */ static const unsigned int kMaxStrlist = 10 * 1000 * 1000; static struct mystr s_null_str; static int sort_compare_func(const void* p1, const void* p2); static int sort_compare_func_reverse(const void* p1, const void* p2); static int sort_compare_common(const void* p1, const void* p2, int reverse); void str_list_free(struct mystr_list* p_list) { unsigned int i; for (i=0; i < p_list->list_len; ++i) { str_free(&p_list->p_nodes[i].str); str_free(&p_list->p_nodes[i].sort_key_str); } p_list->list_len = 0; p_list->alloc_len = 0; if (p_list->p_nodes) { vsf_sysutil_free(p_list->p_nodes); p_list->p_nodes = 0; } } unsigned int str_list_get_length(const struct mystr_list* p_list) { return p_list->list_len; } int str_list_contains_str(const struct mystr_list* p_list, const struct mystr* p_str) { unsigned int i; for (i=0; i < p_list->list_len; ++i) { if (str_equal(p_str, &p_list->p_nodes[i].str)) { return 1; } } return 0; } void str_list_add(struct mystr_list* p_list, const struct mystr* p_str, const struct mystr* p_sort_key_str) { struct mystr_list_node* p_node; /* Expand the node allocation if we have to */ if (p_list->list_len == p_list->alloc_len) { if (p_list->alloc_len == 0) { p_list->alloc_len = 32; p_list->p_nodes = vsf_sysutil_malloc( p_list->alloc_len * (unsigned int) sizeof(struct mystr_list_node)); } else { p_list->alloc_len *= 2; if (p_list->alloc_len > kMaxStrlist) { die("excessive strlist"); } p_list->p_nodes = vsf_sysutil_realloc( p_list->p_nodes, p_list->alloc_len * (unsigned int) sizeof(struct mystr_list_node)); } } p_node = &p_list->p_nodes[p_list->list_len]; p_node->str = s_null_str; p_node->sort_key_str = s_null_str; str_copy(&p_node->str, p_str); if (p_sort_key_str) { str_copy(&p_node->sort_key_str, p_sort_key_str); } p_list->list_len++; } void str_list_sort(struct mystr_list* p_list, int reverse) { if (!reverse) { vsf_sysutil_qsort(p_list->p_nodes, p_list->list_len, sizeof(struct mystr_list_node), sort_compare_func); } else { vsf_sysutil_qsort(p_list->p_nodes, p_list->list_len, sizeof(struct mystr_list_node), sort_compare_func_reverse); } } static int sort_compare_func(const void* p1, const void* p2) { return sort_compare_common(p1, p2, 0); } static int sort_compare_func_reverse(const void* p1, const void* p2) { return sort_compare_common(p1, p2, 1); } static int sort_compare_common(const void* p1, const void* p2, int reverse) { const struct mystr* p_cmp1; const struct mystr* p_cmp2; const struct mystr_list_node* p_node1 = (const struct mystr_list_node*) p1; const struct mystr_list_node* p_node2 = (const struct mystr_list_node*) p2; if (!str_isempty(&p_node1->sort_key_str)) { p_cmp1 = &p_node1->sort_key_str; } else { p_cmp1 = &p_node1->str; } if (!str_isempty(&p_node2->sort_key_str)) { p_cmp2 = &p_node2->sort_key_str; } else { p_cmp2 = &p_node2->str; } if (reverse) { return str_strcmp(p_cmp2, p_cmp1); } else { return str_strcmp(p_cmp1, p_cmp2); } } const struct mystr* str_list_get_pstr(const struct mystr_list* p_list, unsigned int indexx) { if (indexx >= p_list->list_len) { bug("indexx out of range in str_list_get_str"); } return &p_list->p_nodes[indexx].str; } vsftpd-3.0.3/port/0000755000175000017500000000000010750743677013031 5ustar chrischrisvsftpd-3.0.3/port/cmsg_extras.h0000644000175000017500000000100510750743677015515 0ustar chrischris#ifndef VSF_CMSG_EXTRAS_H #define VSF_CMSG_EXTRAS_H #include #include /* These are from Linux glibc-2.2 */ #ifndef CMSG_ALIGN #define CMSG_ALIGN(len) (((len) + sizeof (size_t) - 1) \ & ~(sizeof (size_t) - 1)) #endif #ifndef CMSG_SPACE #define CMSG_SPACE(len) (CMSG_ALIGN (len) \ + CMSG_ALIGN (sizeof (struct cmsghdr))) #endif #ifndef CMSG_LEN #define CMSG_LEN(len) (CMSG_ALIGN (sizeof (struct cmsghdr)) + (len)) #endif #endif /* VSF_CMSG_EXTRAS_H */ vsftpd-3.0.3/port/hpux_bogons.h0000644000175000017500000000070210750743677015534 0ustar chrischris#ifndef VSF_HPUX_BOGONS_H #define VSF_HPUX_BOGONS_H #include /* HP-UX has MAP_ANONYMOUS but not MAP_ANON - I'm not sure which is more * standard! */ #ifdef MAP_ANONYMOUS #ifndef MAP_ANON #define MAP_ANON MAP_ANONYMOUS #endif #endif /* Ancient versions of HP-UX don't have MAP_FAILED */ #ifndef MAP_FAILED #define MAP_FAILED (void *) -1L #endif /* Need dirfd() */ #include "dirfd_extras.h" #endif /* VSF_HPUX_BOGONS_H */ vsftpd-3.0.3/port/solaris_bogons.h0000644000175000017500000000070510750743677016227 0ustar chrischris#ifndef VSF_SOLARIS_BOGONS_H #define VSF_SOLARIS_BOGONS_H /* This bogon ensures we get access to CMSG_DATA, CMSG_FIRSTHDR */ #define _XPG4_2 /* This bogon prevents _XPG4_2 breaking the include of signal.h! */ #define __EXTENSIONS__ /* Safe to always enable 64-bit file support. */ #define _FILE_OFFSET_BITS 64 #define _LARGEFILE_SOURCE 1 #define _LARGEFILE64_SOURCE 1 /* Need dirfd() */ #include "dirfd_extras.h" #endif /* VSF_SOLARIS_BOGONS_H */ vsftpd-3.0.3/port/dirfd_extras.h0000644000175000017500000000016710750743677015664 0ustar chrischris#ifndef VSF_DIRFD_EXTRAS_H #define VSF_DIRFD_EXTRAS_H #define dirfd(x) ((x)->dd_fd) #endif /* VSF_DIRFD_EXTRAS_H */ vsftpd-3.0.3/port/porting_junk.h0000644000175000017500000000067710750743677015725 0ustar chrischris#ifndef VSF_PORTINGJUNK_H #define VSF_PORTINGJUNK_H #ifdef __sun #include "solaris_bogons.h" #endif #ifdef __sgi #include "irix_bogons.h" #endif #ifdef __hpux #include "hpux_bogons.h" #endif #ifdef _AIX #include "aix_bogons.h" #endif #ifdef __osf__ #include "tru64_bogons.h" #endif /* So many older systems lack these, that it's too much hassle to list all * the errant systems */ #include "cmsg_extras.h" #endif /* VSF_PORTINGJUNK_H */ vsftpd-3.0.3/port/tru64_bogons.h0000644000175000017500000000020610750743677015533 0ustar chrischris#ifndef VSF_TRU64_BOGONS_H #define VSF_TRU64_BOGONS_H /* Need dirfd() */ #include "dirfd_extras.h" #endif /* VSF_TRU64_BOGONS_H */ vsftpd-3.0.3/port/irix_bogons.h0000644000175000017500000000020310750743677015517 0ustar chrischris#ifndef VSF_IRIX_BOGONS_H #define VSF_IRIX_BOGONS_H /* Need dirfd() */ #include "dirfd_extras.h" #endif /* VSF_IRIX_BOGONS_H */ vsftpd-3.0.3/port/aix_bogons.h0000644000175000017500000000020010750743677015322 0ustar chrischris#ifndef VSF_AIX_BOGONS_H #define VSF_AIX_BOGONS_H /* Need dirfd() */ #include "dirfd_extras.h" #endif /* VSF_AIX_BOGONS_H */ vsftpd-3.0.3/INSTALL0000644000175000017500000001261010750743677013076 0ustar chrischrisINSTALL ======= This file details how to build and install / run vsftpd from the vsftpd distribution .tar.gz file. Step 1) Build vsftpd. Switch to the directory created when you unpacked the vsftpd .tar.gz file. e.g.: cd vsftpd-1.1.2 edit "builddefs.h" to handle compile-time settings (tcp_wrappers build, etc). Just type "make" (and mail me to fix it if it doesn't build ;-). This should produce you a vsftpd binary. You can test for this, e.g.: [chris@localhost vsftpd]$ ls -l vsftpd -rwxrwxr-x 1 chris chris 61748 Sep 27 00:26 vsftpd Step 2) Satisfy vsftpd pre-requisites 2a) vsftpd needs the user "nobody" in the default configuration. Add this user in case it does not already exist. e.g.: [root@localhost root]# useradd nobody useradd: user nobody exists 2b) vsftpd needs the (empty) directory /usr/share/empty in the default configuration. Add this directory in case it does not already exist. e.g.: [root@localhost root]# mkdir /usr/share/empty/ mkdir: cannot create directory `/usr/share/empty': File exists 2c) For anonymous FTP, you will need the user "ftp" to exist, and have a valid home directory (which is NOT owned or writable by the user "ftp"). The following commands could be used to set up the user "ftp" if you do not have one: [root@localhost root]# mkdir /var/ftp/ [root@localhost root]# useradd -d /var/ftp ftp (the next two are useful to run even if the user "ftp" already exists). [root@localhost root]# chown root.root /var/ftp [root@localhost root]# chmod og-w /var/ftp Step 3) Install vsftpd config file, executable, man page, etc. Running "make install" will try to copy the binary, man pages, etc. to somewhere sensible. Or you might want to copy these things by hand, e.g.: cp vsftpd /usr/local/sbin/vsftpd cp vsftpd.conf.5 /usr/local/man/man5 cp vsftpd.8 /usr/local/man/man8 "make install" doesn't copy the sample config file. It is recommended you do this: cp vsftpd.conf /etc Step 4) Smoke test (without an inetd). vsftpd can run standalone or via an inetd (such as inetd or xinetd). You will typically get more control running vsftpd from an inetd. But first we will run it without, so we can check things are going well so far. Edit /etc/vsftpd.conf, and add this line at the bottom: listen=YES This tells vsftpd it will NOT be running from inetd. Right, now let's try and run it! Log in as root. Make sure you are not running other FTP servers (or vsftpd will not be able to use the FTP port, 21). Run the binary from wherever you put it, e.g.: [root@localhost root]# /usr/local/sbin/vsftpd & [1] 2104 If all is well, you can now connect! e.g.: [chris@localhost chris]$ ftp localhost Connected to localhost (127.0.0.1). 220 (vsFTPd 1.1.1) Name (localhost:chris): ftp 331 Please specify the password. Password: 230 Login successful. Have fun. Remote system type is UNIX. Using binary mode to transfer files. ftp> ls 227 Entering Passive Mode (127,0,0,1,229,133) 150 Here comes the directory listing. d--x--x--x 2 0 0 4096 Jan 14 2002 bin d--x--x--x 2 0 0 4096 Apr 21 20:52 etc drwxr-xr-x 2 0 0 4096 Apr 21 20:52 lib drwxr-sr-x 2 0 50 4096 Jul 26 22:58 pub 226 Directory send OK. ftp> Step 5) Run from an inetd of some kind (optional - standalone mode is now recommended) You may want to run the binary from an inetd of some kind, because this can give you extra features - e.g. xinetd has a lot of settings. (Note that vsftpd's inbuilt listener covers most of the more useful xinetd settings). 5a) If using standard "inetd", you will need to edit /etc/inetd.conf, and add a line such as: ftp stream tcp nowait root /usr/sbin/tcpd /usr/local/sbin/vsftpd (Make sure to remove or comment out any existing ftp service lines. If you don't have tcp_wrappers installed, or don't want to use them, take out the /usr/sbin/tcpd part). inetd will need to be told to reload its config file: kill -SIGHUP `pidof inetd` 5b) If using "xinetd", you can follow a provided example, by looking at the file EXAMPLE/INTERNET_SITE/README. Various other examples show how to leverage the more powerful xinetd features. Step 6) Set up PAM for local logins (optional) If you are running vsftpd on a PAM enabled machine, you will need to have a /etc/pam.d/ftp file present, otherwise non-anonymous logins will fail. [NOTE - if you have an older version of PAM, that file might be /etc/pam.conf] For a standard setup, you can just copy a provided example file: cp RedHat/vsftpd.pam /etc/pam.d/ftp Step 7) Customize your configuration As well as the above three pre-requisites, you are recommended to install a config file. The default location for the config file is /etc/vsftpd.conf. There is a sample vsftpd.conf in the distribution tarball. You probably want to copy that to /etc/vsftpd.conf as a basis for modification, i.e.: cp vsftpd.conf /etc The default configuration allows neither local user logins nor anonymous uploads. You may wish to change these defaults. Other notes =========== Tested platforms (well, it builds) - Any modern, well featured platform should work fine! Recent versions of the platforms listed below, and often older ones, should work fine. - Fedora Core - RedHat Linux - RedHat Enterprise Linux - Solaris / GNU tools (Solaris 8 or newer) - SuSE Linux - Debian Linux - OpenBSD - FreeBSD - NetBSD - HP-UX / GNU tools - IRIX / GNU tools - AIX / GNU tools - Mac OS X (note; older versions have setgroups() problem. 10.3.4 reported OK) vsftpd-3.0.3/secbuf.h0000644000175000017500000000154110750743677013466 0ustar chrischris#ifndef VSF_SECBUF_H #define VSF_SECBUF_H /* vsf_secbuf_alloc() * PURPOSE * Allocate a "secure buffer". A secure buffer is one which will attempt to * catch out of bounds accesses by crashing the program (rather than * corrupting memory). It works by using UNIX memory protection. It isn't * foolproof. * PARAMETERS * p_ptr - pointer to a pointer which is to contain the secure buffer. * Any previous buffer pointed to is freed. * size - size in bytes required for the secure buffer. */ void vsf_secbuf_alloc(char** p_ptr, unsigned int size); /* vsf_secbuf_free() * PURPOSE * Frees a "secure buffer". * PARAMETERS * p_ptr - pointer to a pointer containing the buffer to be freed. The * buffer pointer is nullified by this call. */ void vsf_secbuf_free(char** p_ptr); #endif /* VSF_SECBUF_H */ vsftpd-3.0.3/ssl.h0000644000175000017500000000224311120351744012776 0ustar chrischris#ifndef VSF_SSL_H #define VSF_SSL_H struct vsf_session; struct mystr; int ssl_read(struct vsf_session* p_sess, void* p_ssl, char* p_buf, unsigned int len); int ssl_peek(struct vsf_session* p_sess, void* p_ssl, char* p_buf, unsigned int len); int ssl_write(void* p_ssl, const char* p_buf, unsigned int len); int ssl_write_str(void* p_ssl, const struct mystr* p_str); int ssl_read_into_str(struct vsf_session* p_sess, void* p_ssl, struct mystr* p_str); void ssl_init(struct vsf_session* p_sess); int ssl_accept(struct vsf_session* p_sess, int fd); int ssl_data_close(struct vsf_session* p_sess); void ssl_comm_channel_init(struct vsf_session* p_sess); void ssl_comm_channel_set_consumer_context(struct vsf_session* p_sess); void ssl_comm_channel_set_producer_context(struct vsf_session* p_sess); void handle_auth(struct vsf_session* p_sess); void handle_pbsz(struct vsf_session* p_sess); void handle_prot(struct vsf_session* p_sess); void ssl_control_handshake(struct vsf_session* p_sess); void ssl_add_entropy(struct vsf_session* p_sess); #endif /* VSF_SSL_H */ vsftpd-3.0.3/vsftpd.conf0000644000175000017500000001076111673157150014215 0ustar chrischris# Example config file /etc/vsftpd.conf # # The default compiled in settings are fairly paranoid. This sample file # loosens things up a bit, to make the ftp daemon more usable. # Please see vsftpd.conf.5 for all compiled in defaults. # # READ THIS: This example file is NOT an exhaustive list of vsftpd options. # Please read the vsftpd.conf.5 manual page to get a full idea of vsftpd's # capabilities. # # Allow anonymous FTP? (Beware - allowed by default if you comment this out). anonymous_enable=YES # # Uncomment this to allow local users to log in. #local_enable=YES # # Uncomment this to enable any form of FTP write command. #write_enable=YES # # Default umask for local users is 077. You may wish to change this to 022, # if your users expect that (022 is used by most other ftpd's) #local_umask=022 # # Uncomment this to allow the anonymous FTP user to upload files. This only # has an effect if the above global write enable is activated. Also, you will # obviously need to create a directory writable by the FTP user. #anon_upload_enable=YES # # Uncomment this if you want the anonymous FTP user to be able to create # new directories. #anon_mkdir_write_enable=YES # # Activate directory messages - messages given to remote users when they # go into a certain directory. dirmessage_enable=YES # # Activate logging of uploads/downloads. xferlog_enable=YES # # Make sure PORT transfer connections originate from port 20 (ftp-data). connect_from_port_20=YES # # If you want, you can arrange for uploaded anonymous files to be owned by # a different user. Note! Using "root" for uploaded files is not # recommended! #chown_uploads=YES #chown_username=whoever # # You may override where the log file goes if you like. The default is shown # below. #xferlog_file=/var/log/vsftpd.log # # If you want, you can have your log file in standard ftpd xferlog format. # Note that the default log file location is /var/log/xferlog in this case. #xferlog_std_format=YES # # You may change the default value for timing out an idle session. #idle_session_timeout=600 # # You may change the default value for timing out a data connection. #data_connection_timeout=120 # # It is recommended that you define on your system a unique user which the # ftp server can use as a totally isolated and unprivileged user. #nopriv_user=ftpsecure # # Enable this and the server will recognise asynchronous ABOR requests. Not # recommended for security (the code is non-trivial). Not enabling it, # however, may confuse older FTP clients. #async_abor_enable=YES # # By default the server will pretend to allow ASCII mode but in fact ignore # the request. Turn on the below options to have the server actually do ASCII # mangling on files when in ASCII mode. # Beware that on some FTP servers, ASCII support allows a denial of service # attack (DoS) via the command "SIZE /big/file" in ASCII mode. vsftpd # predicted this attack and has always been safe, reporting the size of the # raw file. # ASCII mangling is a horrible feature of the protocol. #ascii_upload_enable=YES #ascii_download_enable=YES # # You may fully customise the login banner string: #ftpd_banner=Welcome to blah FTP service. # # You may specify a file of disallowed anonymous e-mail addresses. Apparently # useful for combatting certain DoS attacks. #deny_email_enable=YES # (default follows) #banned_email_file=/etc/vsftpd.banned_emails # # You may specify an explicit list of local users to chroot() to their home # directory. If chroot_local_user is YES, then this list becomes a list of # users to NOT chroot(). # (Warning! chroot'ing can be very dangerous. If using chroot, make sure that # the user does not have write access to the top level directory within the # chroot) #chroot_local_user=YES #chroot_list_enable=YES # (default follows) #chroot_list_file=/etc/vsftpd.chroot_list # # You may activate the "-R" option to the builtin ls. This is disabled by # default to avoid remote users being able to cause excessive I/O on large # sites. However, some broken FTP clients such as "ncftp" and "mirror" assume # the presence of the "-R" option, so there is a strong case for enabling it. #ls_recurse_enable=YES # # When "listen" directive is enabled, vsftpd runs in standalone mode and # listens on IPv4 sockets. This directive cannot be used in conjunction # with the listen_ipv6 directive. listen=YES # # This directive enables listening on IPv6 sockets. To listen on IPv4 and IPv6 # sockets, you must run two copies of vsftpd with two configuration files. # Make sure, that one of the listen options is commented !! #listen_ipv6=YES vsftpd-3.0.3/.ftpcmdio.h.swp0000644000175000017500000003000012553761066014675 0ustar chrischrisb0VIM 7.4r€zOó4P8ichrislocalhost.localdomain~chris/vsftpd-3.0.3pre1/ftpcmdio.h 3210#"! Utpaadm aéÒÑï®”‰?($í⯡tH#â ¸ · › K 0 " õ É ¤   \ + *   ¾ v S E  ó ï ¥ ¤ Š  1 ê æ ¤ g f M B úÙÕ”_^>3í¡ZVÒѸ­cíß²®xwXMýïÂ}7êæ«s+*  #endif /* VSF_FTPCMDIO_H */ struct mystr* p_arg_str, int set_alarm); struct mystr* p_cmd_str,void vsf_cmdio_get_cmd_and_arg(struct vsf_session* p_sess, */ * set_alarm - If true, the control connection inactivity monitor is used * p_arg_str - Where to put the FTP argument string (may be empty) * p_cmd_str - Where to put the FTP command string (may be empty) * p_sess - The current session object * PARAMETERS * Read an FTP command (and optional argument) from the FTP control connection. * PURPOSE/* vsf_cmdio_get_cmd_and_arg()void vsf_cmdio_set_alarm(struct vsf_session* p_sess); */ * p_sess - The current session object * PARAMETERS * before _any_ potentially blocking calls. * exposed in the API so that we can play it safe, and activate the alarm * Activate the control connection inactivity timeout. This is explicitly * PURPOSE/* vsf_cmdio_set_alarm() const struct mystr* p_str);void vsf_cmdio_write_str_hyphen(struct vsf_session* p_sess, int status, */ * the response text. This indicates there are more lines of response. * output with the continuation indicator '-' between the response code and * The same as vsf_cmdio_write_str(), apart from the response line is * PURPOSE/* vsf_cmdio_write_str_hyphen() const struct mystr* p_str);void vsf_cmdio_write_str(struct vsf_session* p_sess, int status, */ * string buffer object "p_str". * The same as vsf_cmdio_write(), apart from the text is specified as a * PURPOSE/* vsf_cmdio_write_str() const char* p_text, int exit_val);void vsf_cmdio_write_exit(struct vsf_session* p_sess, int status, */ * write is _guaranteed_ to not block (ditching output if neccessary). * The same as vsf_cmdio_write(), and then the calling process is exited. The * PURPOSE/* vsf_cmdio_write_exit()void vsf_cmdio_write_raw(struct vsf_session* p_sess, const char* p_text); */ * p_text - the text to report * p_sess - the current session object * PARAMETERS * newline characters if required. * not prepended, and it is also the client's responsibility to include * Write a raw response to the FTP control connection. A status code is * PURPOSE/* vsf_cmdio_write_raw() const char* p_text);void vsf_cmdio_write_hyphen(struct vsf_session* p_sess, int status, */ * p_text - the text to report * status - the status code to report * p_sess - the current session object * PARAMETERS * continuation indicator. * Write a response to the FTP control connection, with a hyphen '-' * PURPOSE/* vsf_cmdio_write_hyphen() const char* p_text);void vsf_cmdio_write(struct vsf_session* p_sess, int status, */ * p_text - the text to report * status - the status code to report * p_sess - the current session object * PARAMETERS * Write a response to the FTP control connection. * PURPOSE/* vsf_cmdio_write()void vsf_cmdio_sock_setup(void); */ * control connection. * Initialise a few socket settings (keepalive, nonagle, etc). on the FTP * PURPOSE/* vsf_cmdio_sock_setup()struct vsf_session;struct mystr;#define VSF_FTPCMDIO_H#ifndef VSF_FTPCMDIO_Hvsftpd-3.0.3/str.c0000644000175000017500000003620612025275422013011 0ustar chrischris/* * Part of Very Secure FTPd * Licence: GPL v2 * Author: Chris Evans * str.c * * Generic string handling functions. The fact that a string is implemented * internally using a buffer is not exposed in the API. If you can't see * the buffers, you can't handle them in a screwed way. Or so goes the * theory, anyway... */ /* Anti-lamer measures deployed, sir! */ #define PRIVATE_HANDS_OFF_p_buf p_buf #define PRIVATE_HANDS_OFF_len len #define PRIVATE_HANDS_OFF_alloc_bytes alloc_bytes #include "str.h" /* Ick. Its for die() */ #include "utility.h" #include "sysutil.h" /* File local functions */ static void str_split_text_common(struct mystr* p_src, struct mystr* p_rhs, const char* p_text, int is_reverse); static int str_equal_internal(const char* p_buf1, unsigned int buf1_len, const char* p_buf2, unsigned int buf2_len); /* Private functions */ static void s_setbuf(struct mystr* p_str, char* p_newbuf) { if (p_str->p_buf != 0) { bug("p_buf not NULL when setting it"); } p_str->p_buf = p_newbuf; } void private_str_alloc_memchunk(struct mystr* p_str, const char* p_src, unsigned int len) { /* Make sure this will fit in the buffer */ unsigned int buf_needed; if (len + 1 < len) { bug("integer overflow"); } buf_needed = len + 1; if (buf_needed > p_str->alloc_bytes) { str_free(p_str); s_setbuf(p_str, vsf_sysutil_malloc(buf_needed)); p_str->alloc_bytes = buf_needed; } vsf_sysutil_memcpy(p_str->p_buf, p_src, len); p_str->p_buf[len] = '\0'; p_str->len = len; } void private_str_append_memchunk(struct mystr* p_str, const char* p_src, unsigned int len) { unsigned int buf_needed; if (len + p_str->len < len) { bug("integer overflow"); } buf_needed = len + p_str->len; if (buf_needed + 1 < buf_needed) { bug("integer overflow"); } buf_needed++; if (buf_needed > p_str->alloc_bytes) { p_str->p_buf = vsf_sysutil_realloc(p_str->p_buf, buf_needed); p_str->alloc_bytes = buf_needed; } vsf_sysutil_memcpy(p_str->p_buf + p_str->len, p_src, len); p_str->p_buf[p_str->len + len] = '\0'; p_str->len += len; } /* Public functions */ void str_alloc_text(struct mystr* p_str, const char* p_src) { unsigned int len = vsf_sysutil_strlen(p_src); private_str_alloc_memchunk(p_str, p_src, len); } void str_copy(struct mystr* p_dest, const struct mystr* p_src) { private_str_alloc_memchunk(p_dest, p_src->p_buf, p_src->len); } const char* str_strdup(const struct mystr* p_str) { return vsf_sysutil_strdup(str_getbuf(p_str)); } void str_alloc_alt_term(struct mystr* p_str, const char* p_src, char term) { const char* p_search = p_src; unsigned int len = 0; while (*p_search != term) { p_search++; len++; if (len == 0) { bug("integer overflow"); } } private_str_alloc_memchunk(p_str, p_src, len); } void str_alloc_ulong(struct mystr* p_str, unsigned long the_long) { str_alloc_text(p_str, vsf_sysutil_ulong_to_str(the_long)); } void str_alloc_filesize_t(struct mystr* p_str, filesize_t the_filesize) { str_alloc_text(p_str, vsf_sysutil_filesize_t_to_str(the_filesize)); } void str_free(struct mystr* p_str) { if (p_str->p_buf != 0) { vsf_sysutil_free(p_str->p_buf); } p_str->p_buf = 0; p_str->len = 0; p_str->alloc_bytes = 0; } void str_empty(struct mystr* p_str) { /* Ensure a buffer is allocated. */ (void) str_getbuf(p_str); str_trunc(p_str, 0); } void str_trunc(struct mystr* p_str, unsigned int trunc_len) { if (trunc_len >= p_str->alloc_bytes) { bug("trunc_len not smaller than alloc_bytes in str_trunc"); } p_str->len = trunc_len; p_str->p_buf[p_str->len] = '\0'; } void str_reserve(struct mystr* p_str, unsigned int res_len) { /* Reserve space for the trailing zero as well. */ res_len++; if (res_len == 0) { bug("integer overflow"); } if (res_len > p_str->alloc_bytes) { p_str->p_buf = vsf_sysutil_realloc(p_str->p_buf, res_len); p_str->alloc_bytes = res_len; } p_str->p_buf[res_len - 1] = '\0'; } int str_isempty(const struct mystr* p_str) { return (p_str->len == 0); } unsigned int str_getlen(const struct mystr* p_str) { return p_str->len; } const char* str_getbuf(const struct mystr* p_str) { if (p_str->p_buf == 0) { if (p_str->len != 0 || p_str->alloc_bytes != 0) { bug("p_buf NULL and len or alloc_bytes != 0 in str_getbuf"); } private_str_alloc_memchunk((struct mystr*)p_str, 0, 0); } return p_str->p_buf; } int str_strcmp(const struct mystr* p_str1, const struct mystr* p_str2) { return str_equal_internal(p_str1->p_buf, p_str1->len, p_str2->p_buf, p_str2->len); } static int str_equal_internal(const char* p_buf1, unsigned int buf1_len, const char* p_buf2, unsigned int buf2_len) { int retval; unsigned int minlen = buf1_len; if (buf2_len < minlen) { minlen = buf2_len; } retval = vsf_sysutil_memcmp(p_buf1, p_buf2, minlen); if (retval != 0 || buf1_len == buf2_len) { return retval; } /* Strings equal but lengths differ. The greater one, then, is the longer */ return (int) (buf1_len - buf2_len); } int str_equal(const struct mystr* p_str1, const struct mystr* p_str2) { return (str_strcmp(p_str1, p_str2) == 0); } int str_equal_text(const struct mystr* p_str, const char* p_text) { unsigned int cmplen = vsf_sysutil_strlen(p_text); return (str_equal_internal(p_str->p_buf, p_str->len, p_text, cmplen) == 0); } void str_append_str(struct mystr* p_str, const struct mystr* p_other) { private_str_append_memchunk(p_str, p_other->p_buf, p_other->len); } void str_append_text(struct mystr* p_str, const char* p_src) { unsigned int len = vsf_sysutil_strlen(p_src); private_str_append_memchunk(p_str, p_src, len); } void str_append_char(struct mystr* p_str, char the_char) { private_str_append_memchunk(p_str, &the_char, sizeof(the_char)); } void str_append_ulong(struct mystr* p_str, unsigned long the_ulong) { str_append_text(p_str, vsf_sysutil_ulong_to_str(the_ulong)); } void str_append_filesize_t(struct mystr* p_str, filesize_t the_filesize) { str_append_text(p_str, vsf_sysutil_filesize_t_to_str(the_filesize)); } void str_append_double(struct mystr* p_str, double the_double) { str_append_text(p_str, vsf_sysutil_double_to_str(the_double)); } void str_upper(struct mystr* p_str) { unsigned int i; for (i=0; i < p_str->len; i++) { p_str->p_buf[i] = (char) vsf_sysutil_toupper(p_str->p_buf[i]); } } void str_rpad(struct mystr* p_str, const unsigned int min_width) { unsigned int to_pad; if (p_str->len >= min_width) { return; } to_pad = min_width - p_str->len; while (to_pad--) { str_append_char(p_str, ' '); } } void str_lpad(struct mystr* p_str, const unsigned int min_width) { static struct mystr s_tmp_str; unsigned int to_pad; if (p_str->len >= min_width) { return; } to_pad = min_width - p_str->len; str_empty(&s_tmp_str); while (to_pad--) { str_append_char(&s_tmp_str, ' '); } str_append_str(&s_tmp_str, p_str); str_copy(p_str, &s_tmp_str); } void str_replace_char(struct mystr* p_str, char from, char to) { unsigned int i; for (i=0; i < p_str->len; i++) { if (p_str->p_buf[i] == from) { p_str->p_buf[i] = to; } } } void str_replace_text(struct mystr* p_str, const char* p_from, const char* p_to) { static struct mystr s_lhs_chunk_str; static struct mystr s_rhs_chunk_str; unsigned int lhs_len; str_copy(&s_lhs_chunk_str, p_str); str_free(p_str); do { lhs_len = str_getlen(&s_lhs_chunk_str); str_split_text(&s_lhs_chunk_str, &s_rhs_chunk_str, p_from); /* Copy lhs to destination */ str_append_str(p_str, &s_lhs_chunk_str); /* If this was a 'hit', append the 'to' text */ if (str_getlen(&s_lhs_chunk_str) < lhs_len) { str_append_text(p_str, p_to); } /* Current rhs becomes new lhs */ str_copy(&s_lhs_chunk_str, &s_rhs_chunk_str); } while (!str_isempty(&s_lhs_chunk_str)); } void str_split_char(struct mystr* p_src, struct mystr* p_rhs, char c) { /* Just use str_split_text */ char ministr[2]; ministr[0] = c; ministr[1] = '\0'; str_split_text(p_src, p_rhs, ministr); } void str_split_char_reverse(struct mystr* p_src, struct mystr* p_rhs, char c) { /* Just use str_split_text_reverse */ char ministr[2]; ministr[0] = c; ministr[1] = '\0'; str_split_text_reverse(p_src, p_rhs, ministr); } void str_split_text(struct mystr* p_src, struct mystr* p_rhs, const char* p_text) { str_split_text_common(p_src, p_rhs, p_text, 0); } void str_split_text_reverse(struct mystr* p_src, struct mystr* p_rhs, const char* p_text) { str_split_text_common(p_src, p_rhs, p_text, 1); } static void str_split_text_common(struct mystr* p_src, struct mystr* p_rhs, const char* p_text, int is_reverse) { struct str_locate_result locate_result; unsigned int indexx; unsigned int search_len = vsf_sysutil_strlen(p_text); if (is_reverse) { locate_result = str_locate_text_reverse(p_src, p_text); } else { locate_result = str_locate_text(p_src, p_text); } /* Not found? */ if (!locate_result.found) { str_empty(p_rhs); return; } indexx = locate_result.index; if (indexx + search_len > p_src->len) { bug("indexx invalid in str_split_text"); } /* Build rhs */ private_str_alloc_memchunk(p_rhs, p_src->p_buf + indexx + search_len, p_src->len - indexx - search_len); /* Build lhs */ str_trunc(p_src, indexx); } struct str_locate_result str_locate_str(const struct mystr* p_str, const struct mystr* p_look_str) { return str_locate_text(p_str, str_getbuf(p_look_str)); } struct str_locate_result str_locate_str_reverse(const struct mystr* p_str, const struct mystr* p_look_str) { return str_locate_text_reverse(p_str, str_getbuf(p_look_str)); } struct str_locate_result str_locate_char(const struct mystr* p_str, char look_char) { char look_str[2]; look_str[0] = look_char; look_str[1] = '\0'; return str_locate_text(p_str, look_str); } struct str_locate_result str_locate_chars(const struct mystr* p_str, const char* p_chars) { struct str_locate_result retval; unsigned int num_chars = vsf_sysutil_strlen(p_chars); unsigned int i = 0; retval.found = 0; retval.char_found = 0; retval.index = 0; for (; i < p_str->len; ++i) { unsigned int j = 0; char this_char = p_str->p_buf[i]; for (; j < num_chars; ++j) { if (p_chars[j] == this_char) { retval.found = 1; retval.index = i; retval.char_found = p_chars[j]; return retval; } } } return retval; } struct str_locate_result str_locate_text(const struct mystr* p_str, const char* p_text) { struct str_locate_result retval; unsigned int i; unsigned int text_len = vsf_sysutil_strlen(p_text); retval.found = 0; retval.char_found = 0; retval.index = 0; if (text_len == 0 || text_len > p_str->len) { /* Not found */ return retval; } for (i=0; i <= (p_str->len - text_len); i++) { if (vsf_sysutil_memcmp(p_str->p_buf + i, p_text, text_len) == 0) { retval.found = 1; retval.index = i; return retval; } } /* Not found */ return retval; } struct str_locate_result str_locate_text_reverse(const struct mystr* p_str, const char* p_text) { struct str_locate_result retval; unsigned int i; unsigned int text_len = vsf_sysutil_strlen(p_text); retval.found = 0; retval.char_found = 0; retval.index = 0; if (text_len == 0 || text_len > p_str->len) { return retval; } i = p_str->len - text_len; /* Want to go through loop once even if i==0 */ while (1) { if (vsf_sysutil_memcmp(p_str->p_buf + i, p_text, text_len) == 0) { retval.found = 1; retval.index = i; return retval; } if (i == 0) { break; } i--; } /* Not found */ return retval; } void str_left(const struct mystr* p_str, struct mystr* p_out, unsigned int chars) { if (chars > p_str->len) { bug("chars invalid in str_left"); } private_str_alloc_memchunk(p_out, p_str->p_buf, chars); } void str_right(const struct mystr* p_str, struct mystr* p_out, unsigned int chars) { unsigned int indexx = p_str->len - chars; if (chars > p_str->len) { bug("chars invalid in str_right"); } private_str_alloc_memchunk(p_out, p_str->p_buf + indexx, chars); } void str_mid_to_end(const struct mystr* p_str, struct mystr* p_out, unsigned int indexx) { if (indexx > p_str->len) { bug("invalid indexx in str_mid_to_end"); } private_str_alloc_memchunk(p_out, p_str->p_buf + indexx, p_str->len - indexx); } char str_get_char_at(const struct mystr* p_str, const unsigned int indexx) { if (indexx >= p_str->len) { bug("bad indexx in str_get_char_at"); } return p_str->p_buf[indexx]; } int str_contains_space(const struct mystr* p_str) { unsigned int i; for (i=0; i < p_str->len; i++) { if (vsf_sysutil_isspace(p_str->p_buf[i])) { return 1; } } return 0; } int str_all_space(const struct mystr* p_str) { unsigned int i; for (i=0; i < p_str->len; i++) { if (!vsf_sysutil_isspace(p_str->p_buf[i])) { return 0; } } return 1; } int str_contains_unprintable(const struct mystr* p_str) { unsigned int i; for (i=0; i < p_str->len; i++) { if (!vsf_sysutil_isprint(p_str->p_buf[i])) { return 1; } } return 0; } int str_atoi(const struct mystr* p_str) { return vsf_sysutil_atoi(str_getbuf(p_str)); } filesize_t str_a_to_filesize_t(const struct mystr* p_str) { return vsf_sysutil_a_to_filesize_t(str_getbuf(p_str)); } unsigned int str_octal_to_uint(const struct mystr* p_str) { return vsf_sysutil_octal_to_uint(str_getbuf(p_str)); } int str_getline(const struct mystr* p_str, struct mystr* p_line_str, unsigned int* p_pos) { unsigned int start_pos = *p_pos; unsigned int curr_pos = start_pos; unsigned int buf_len = str_getlen(p_str); const char* p_buf = str_getbuf(p_str); unsigned int out_len; if (start_pos > buf_len) { bug("p_pos out of range in str_getline"); } str_empty(p_line_str); if (start_pos == buf_len) { return 0; } while (curr_pos < buf_len && p_buf[curr_pos] != '\n') { curr_pos++; if (curr_pos == 0) { bug("integer overflow"); } } out_len = curr_pos - start_pos; /* If we ended on a \n - skip it */ if (curr_pos < buf_len && p_buf[curr_pos] == '\n') { curr_pos++; if (curr_pos == 0) { bug("integer overflow"); } } private_str_alloc_memchunk(p_line_str, p_buf + start_pos, out_len); *p_pos = curr_pos; return 1; } int str_contains_line(const struct mystr* p_str, const struct mystr* p_line_str) { static struct mystr s_curr_line_str; unsigned int pos = 0; while (str_getline(p_str, &s_curr_line_str, &pos)) { if (str_equal(&s_curr_line_str, p_line_str)) { return 1; } } return 0; } void str_replace_unprintable(struct mystr* p_str, char new_char) { unsigned int i; for (i=0; i < p_str->len; i++) { if (!vsf_sysutil_isprint(p_str->p_buf[i])) { p_str->p_buf[i] = new_char; } } } vsftpd-3.0.3/readwrite.h0000644000175000017500000000106411276670763014204 0ustar chrischris#ifndef VSF_READWRITE_H #define VSF_READWRITE_H struct vsf_session; struct mystr; enum EVSFRWTarget { kVSFRWControl = 1, kVSFRWData }; int ftp_write_str(const struct vsf_session* p_sess, const struct mystr* p_str, enum EVSFRWTarget target); int ftp_read_data(struct vsf_session* p_sess, char* p_buf, unsigned int len); int ftp_write_data(const struct vsf_session* p_sess, const char* p_buf, unsigned int len); int ftp_getline(struct vsf_session* p_sess, struct mystr* p_str, char* p_buf); #endif /* VSF_READWRITE_H */ vsftpd-3.0.3/postprivparent.c0000644000175000017500000001125612025247237015302 0ustar chrischris/* * Part of Very Secure FTPd * Licence: GPL v2 * Author: Chris Evans * postprivparent.c * * This file contains all privileged parent services offered after logging * in. This includes e.g. chown() of uploaded files, issuing of port 20 * sockets. */ #include "postprivparent.h" #include "session.h" #include "privops.h" #include "privsock.h" #include "utility.h" #include "tunables.h" #include "defs.h" #include "sysutil.h" #include "str.h" #include "secutil.h" #include "sysstr.h" #include "sysdeputil.h" #include "seccompsandbox.h" static void minimize_privilege(struct vsf_session* p_sess); static void process_post_login_req(struct vsf_session* p_sess); static void cmd_process_chown(struct vsf_session* p_sess); static void cmd_process_get_data_sock(struct vsf_session* p_sess); static void cmd_process_pasv_cleanup(struct vsf_session* p_sess); static void cmd_process_pasv_active(struct vsf_session* p_sess); static void cmd_process_pasv_listen(struct vsf_session* p_sess); static void cmd_process_pasv_accept(struct vsf_session* p_sess); void vsf_priv_parent_postlogin(struct vsf_session* p_sess) { minimize_privilege(p_sess); /* We're still here... */ while (1) { process_post_login_req(p_sess); } } static void process_post_login_req(struct vsf_session* p_sess) { char cmd; /* Blocks */ cmd = priv_sock_get_cmd(p_sess->parent_fd); if (tunable_chown_uploads && cmd == PRIV_SOCK_CHOWN) { cmd_process_chown(p_sess); } else if (cmd == PRIV_SOCK_GET_DATA_SOCK) { cmd_process_get_data_sock(p_sess); } else if (cmd == PRIV_SOCK_PASV_CLEANUP) { cmd_process_pasv_cleanup(p_sess); } else if (cmd == PRIV_SOCK_PASV_ACTIVE) { cmd_process_pasv_active(p_sess); } else if (cmd == PRIV_SOCK_PASV_LISTEN) { cmd_process_pasv_listen(p_sess); } else if (cmd == PRIV_SOCK_PASV_ACCEPT) { cmd_process_pasv_accept(p_sess); } else { die("bad request in process_post_login_req"); } } static void minimize_privilege(struct vsf_session* p_sess) { /* So, we logged in and forked a totally unprivileged child. Our job * now is to minimize the privilege we need in order to act as a helper * to the child. */ if (!p_sess->is_anonymous && tunable_session_support) { /* Need to hang around to update logs, utmp, wtmp etc. on logout. * Need to keep privs to do this. */ return; } { unsigned int caps = 0; struct mystr user_str = INIT_MYSTR; struct mystr dir_str = INIT_MYSTR; if (tunable_nopriv_user) { str_alloc_text(&user_str, tunable_nopriv_user); } if (tunable_secure_chroot_dir) { str_alloc_text(&dir_str, tunable_secure_chroot_dir); } if (tunable_chown_uploads) { caps |= kCapabilityCAP_CHOWN; } if (tunable_connect_from_port_20) { caps |= kCapabilityCAP_NET_BIND_SERVICE; } vsf_secutil_change_credentials(&user_str, &dir_str, 0, caps, VSF_SECUTIL_OPTION_CHROOT); str_free(&user_str); str_free(&dir_str); } seccomp_sandbox_init(); seccomp_sandbox_setup_postlogin_broker(); seccomp_sandbox_lockdown(); } static void cmd_process_chown(struct vsf_session* p_sess) { int the_fd = priv_sock_recv_fd(p_sess->parent_fd); vsf_privop_do_file_chown(p_sess, the_fd); vsf_sysutil_close(the_fd); priv_sock_send_result(p_sess->parent_fd, PRIV_SOCK_RESULT_OK); } static void cmd_process_get_data_sock(struct vsf_session* p_sess) { unsigned short port = (unsigned short) priv_sock_get_int(p_sess->parent_fd); int sock_fd = vsf_privop_get_ftp_port_sock(p_sess, port, 0); if (sock_fd == -1) { priv_sock_send_result(p_sess->parent_fd, PRIV_SOCK_RESULT_BAD); return; } priv_sock_send_result(p_sess->parent_fd, PRIV_SOCK_RESULT_OK); priv_sock_send_fd(p_sess->parent_fd, sock_fd); vsf_sysutil_close(sock_fd); } static void cmd_process_pasv_cleanup(struct vsf_session* p_sess) { vsf_privop_pasv_cleanup(p_sess); priv_sock_send_result(p_sess->parent_fd, PRIV_SOCK_RESULT_OK); } static void cmd_process_pasv_active(struct vsf_session* p_sess) { int active = vsf_privop_pasv_active(p_sess); priv_sock_send_int(p_sess->parent_fd, active); } static void cmd_process_pasv_listen(struct vsf_session* p_sess) { unsigned short port = vsf_privop_pasv_listen(p_sess); priv_sock_send_int(p_sess->parent_fd, port); } static void cmd_process_pasv_accept(struct vsf_session* p_sess) { int fd = vsf_privop_accept_pasv(p_sess); if (fd < 0) { priv_sock_send_result(p_sess->parent_fd, PRIV_SOCK_RESULT_BAD); priv_sock_send_int(p_sess->parent_fd, fd); return; } priv_sock_send_result(p_sess->parent_fd, PRIV_SOCK_RESULT_OK); priv_sock_send_fd(p_sess->parent_fd, fd); vsf_sysutil_close(fd); } vsftpd-3.0.3/banner.h0000644000175000017500000000211110750743701013442 0ustar chrischris#ifndef VSF_BANNER_H #define VSF_BANNER_H struct vsf_session; struct mystr; /* vsf_banner_dir_changed() * PURPOSE * This function, when called, will check if the current directory has just * been entered for the first time in this session. If so, and message file * support is on, a message file is looked for (default .message), and output * to the FTP control connection with the FTP code prefix specified by * "ftpcode". * PARAMETERS * p_sess - the current FTP session object * ftpcode - the FTP code to show with the message */ void vsf_banner_dir_changed(struct vsf_session* p_sess, int ftpcode); /* vsf_banner_write() * PURPOSE * This function, when called, will write the specified string as a multiline * FTP banner, using the specified FTP response code. * PARAMETERS * p_sess - the current FTP session object * p_str - the string to write * ftpcode - the FTP code to show with the message */ void vsf_banner_write(struct vsf_session* p_sess, struct mystr* p_str, int ftpcode); #endif /* VSF_BANNER_H */ vsftpd-3.0.3/postlogin.h0000644000175000017500000000047510750743700014225 0ustar chrischris#ifndef VSF_POSTLOGIN_H #define VSF_POSTLOGIN_H struct vsf_session; /* process_post_login() * PURPOSE * Called to begin FTP protocol parsing for a logged in session. * PARAMETERS * p_sess - the current session object */ void process_post_login(struct vsf_session* p_sess); #endif /* VSF_POSTLOGIN_H */ vsftpd-3.0.3/dummyinc/0000755000175000017500000000000010750743701013656 5ustar chrischrisvsftpd-3.0.3/dummyinc/openssl/0000755000175000017500000000000010750743701015341 5ustar chrischrisvsftpd-3.0.3/dummyinc/openssl/ssl.h0000644000175000017500000000015610750743701016315 0ustar chrischris#ifndef VSF_DUMMYINC_SSL_H #define VSF_DUMMYINC_SSL_H #undef VSF_BUILD_SSL #endif /* VSF_DUMMYINC_SSL_H */ vsftpd-3.0.3/dummyinc/crypt.h0000644000175000017500000000021510750743701015166 0ustar chrischris#ifndef VSF_DUMMYINC_CRYPT_H #define VSF_DUMMYINC_CRYPT_H extern char* crypt(const char*, const char*); #endif /* VSF_DUMMYINC_CRYPT_H */ vsftpd-3.0.3/dummyinc/utmpx.h0000644000175000017500000000017410750743701015206 0ustar chrischris#ifndef VSF_DUMMYINC_UTMPX_H #define VSF_DUMMYINC_UTMPX_H #undef VSF_SYSDEP_HAVE_UTMPX #endif /* VSF_DUMMYINC_UTMPX_H */ vsftpd-3.0.3/dummyinc/security/0000755000175000017500000000000010750743701015525 5ustar chrischrisvsftpd-3.0.3/dummyinc/security/pam_appl.h0000644000175000017500000000020310750743701017462 0ustar chrischris#ifndef VSF_DUMMYINC_PAM_APPL_H #define VSF_DUMMYINC_PAM_APPL_H #undef VSF_SYSDEP_HAVE_PAM #endif /* VSF_DUMMYINC_PAM_APPL_H */ vsftpd-3.0.3/dummyinc/sys/0000755000175000017500000000000011116346116014470 5ustar chrischrisvsftpd-3.0.3/dummyinc/sys/sendfile.h0000644000175000017500000000022010750743701016430 0ustar chrischris#ifndef VSF_DUMMYINC_SENDFILE_H #define VSF_DUMMYINC_SENDFILE_H #undef VSF_SYSDEP_HAVE_SOLARIS_SENDFILE #endif /* VSF_DUMMYINC_SENDFILE_H */ vsftpd-3.0.3/dummyinc/sys/capability.h0000644000175000017500000000021410750743701016763 0ustar chrischris#ifndef VSF_DUMMYINC_CAPABILITY_H #define VSF_DUMMYINC_CAPABILITY_H #undef VSF_SYSDEP_HAVE_LIBCAP #endif /* VSF_DUMMYINC_CAPABILITY_H */ vsftpd-3.0.3/dummyinc/sys/prctl.h0000644000175000017500000000023611116346116015766 0ustar chrischris#ifndef VSF_DUMMYINC_PRCTL_H #define VSF_DUMMYINC_PRCTL_H /* Deliberate nothing; we're just avoiding a compile error. */ #endif /* VSF_DUMMYINC_PRCTL_H */ vsftpd-3.0.3/dummyinc/shadow.h0000644000175000017500000000020010750743701015304 0ustar chrischris#ifndef VSF_DUMMYINC_SHADOW_H #define VSF_DUMMYINC_SHADOW_H #undef VSF_SYSDEP_HAVE_SHADOW #endif /* VSF_DUMMYINC_SHADOW_H */ vsftpd-3.0.3/secbuf.c0000644000175000017500000000450210750743701013445 0ustar chrischris/* * Part of Very Secure FTPd * Licence: GPL v2 * Author: Chris Evans * secbuf.c * * Here are some routines providing the (possibly silly) concept of a secure * buffer. A secure buffer may not be overflowed. A single byte overflow * will cause the program to safely terminate. */ #include "secbuf.h" #include "utility.h" #include "sysutil.h" #include "sysdeputil.h" void vsf_secbuf_alloc(char** p_ptr, unsigned int size) { unsigned int page_offset; unsigned int round_up; char* p_mmap; char* p_no_access_page; unsigned int page_size = vsf_sysutil_getpagesize(); /* Free any previous buffer */ vsf_secbuf_free(p_ptr); /* Round up to next page size */ page_offset = size % page_size; if (page_offset) { unsigned int num_pages = size / page_size; num_pages++; round_up = num_pages * page_size; } else { /* Allocation is on a page-size boundary */ round_up = size; } /* Add on another two pages to make inaccessible */ round_up += page_size * 2; p_mmap = vsf_sysutil_map_anon_pages(round_up); /* Map the first and last page inaccessible */ p_no_access_page = p_mmap + round_up - page_size; vsf_sysutil_memprotect(p_no_access_page, page_size, kVSFSysUtilMapProtNone); /* Before we make the "before" page inaccessible, store the size in it. * A little hack so that we don't need to explicitly be passed the size * when freeing an existing secure buffer */ *((unsigned int*)p_mmap) = round_up; p_no_access_page = p_mmap; vsf_sysutil_memprotect(p_no_access_page, page_size, kVSFSysUtilMapProtNone); p_mmap += page_size; if (page_offset) { p_mmap += (page_size - page_offset); } *p_ptr = p_mmap; } void vsf_secbuf_free(char** p_ptr) { unsigned int map_size; unsigned long page_offset; char* p_mmap = *p_ptr; unsigned int page_size = vsf_sysutil_getpagesize(); if (p_mmap == 0) { return; } /* Calculate the actual start of the mmap region */ page_offset = (unsigned long) p_mmap % page_size; if (page_offset) { p_mmap -= page_offset; } p_mmap -= page_size; /* First make the first page readable so we can get the size */ vsf_sysutil_memprotect(p_mmap, page_size, kVSFSysUtilMapProtReadOnly); /* Extract the mapping size */ map_size = *((unsigned int*)p_mmap); /* Lose the mapping */ vsf_sysutil_memunmap(p_mmap, map_size); } vsftpd-3.0.3/filesize.h0000644000175000017500000000015310750743701014013 0ustar chrischris#ifndef VSF_FILESIZE_H #define VSF_FILESIZE_H typedef long long filesize_t; #endif /* VSF_FILESIZE_H */ vsftpd-3.0.3/utility.c0000644000175000017500000000222312025250345013671 0ustar chrischris/* * Part of Very Secure FTPd * Licence: GPL v2 * Author: Chris Evans * utility.c */ #include "utility.h" #include "sysutil.h" #include "str.h" #include "defs.h" #define DIE_DEBUG void die(const char* p_text) { #ifdef DIE_DEBUG bug(p_text); #endif vsf_sysutil_exit(2); } void die2(const char* p_text1, const char* p_text2) { struct mystr die_str = INIT_MYSTR; str_alloc_text(&die_str, p_text1); if (p_text2) { str_append_text(&die_str, p_text2); } else { str_append_text(&die_str, "(null)"); } die(str_getbuf(&die_str)); } void bug(const char* p_text) { /* Rats. Try and write the reason to the network for diagnostics */ vsf_sysutil_activate_noblock(VSFTP_COMMAND_FD); (void) vsf_sysutil_write_loop(VSFTP_COMMAND_FD, "500 OOPS: ", 10); (void) vsf_sysutil_write_loop(VSFTP_COMMAND_FD, p_text, vsf_sysutil_strlen(p_text)); (void) vsf_sysutil_write_loop(VSFTP_COMMAND_FD, "\r\n", 2); vsf_sysutil_exit(2); } void vsf_exit(const char* p_text) { (void) vsf_sysutil_write_loop(VSFTP_COMMAND_FD, p_text, vsf_sysutil_strlen(p_text)); vsf_sysutil_exit(0); } vsftpd-3.0.3/readwrite.c0000644000175000017500000001203011736433206014160 0ustar chrischris/* * Part of Very Secure FTPd * Licence: GPL v2 * Author: Chris Evans * readwrite.c * * Routines to encapsulate the underlying read / write mechanism (OpenSSL vs. * plain read()/write()). */ #include "readwrite.h" #include "session.h" #include "netstr.h" #include "ssl.h" #include "privsock.h" #include "defs.h" #include "sysutil.h" static int plain_peek_adapter(struct vsf_session* p_sess, char* p_buf, unsigned int len); static int plain_read_adapter(struct vsf_session* p_sess, char* p_buf, unsigned int len); static int ssl_peek_adapter(struct vsf_session* p_sess, char* p_buf, unsigned int len); static int ssl_read_adapter(struct vsf_session* p_sess, char* p_buf, unsigned int len); int ftp_write_str(const struct vsf_session* p_sess, const struct mystr* p_str, enum EVSFRWTarget target) { if (target == kVSFRWData) { if (p_sess->data_use_ssl && p_sess->ssl_slave_active) { int ret = -1; int written; priv_sock_send_cmd(p_sess->ssl_consumer_fd, PRIV_SOCK_DO_SSL_WRITE); priv_sock_send_str(p_sess->ssl_consumer_fd, p_str); written = priv_sock_get_int(p_sess->ssl_consumer_fd); if (written > 0 && written == (int) str_getlen(p_str)) { ret = 0; } return ret; } else if (p_sess->data_use_ssl) { return ssl_write_str(p_sess->p_data_ssl, p_str); } else { return str_netfd_write(p_str, p_sess->data_fd); } } else { if (p_sess->control_use_ssl && p_sess->ssl_slave_active) { priv_sock_send_cmd(p_sess->ssl_consumer_fd, PRIV_SOCK_WRITE_USER_RESP); priv_sock_send_str(p_sess->ssl_consumer_fd, p_str); return priv_sock_get_int(p_sess->ssl_consumer_fd); } else if (p_sess->control_use_ssl) { return ssl_write_str(p_sess->p_control_ssl, p_str); } else { return str_netfd_write(p_str, VSFTP_COMMAND_FD); } } } int ftp_read_data(struct vsf_session* p_sess, char* p_buf, unsigned int len) { if (p_sess->data_use_ssl && p_sess->ssl_slave_active) { int ret; priv_sock_send_cmd(p_sess->ssl_consumer_fd, PRIV_SOCK_DO_SSL_READ); priv_sock_send_int(p_sess->ssl_consumer_fd, len); ret = priv_sock_get_int(p_sess->ssl_consumer_fd); priv_sock_recv_buf(p_sess->ssl_consumer_fd, p_buf, len); /* Need to do this here too because it is useless in the slave process. */ vsf_sysutil_check_pending_actions(kVSFSysUtilIO, ret, p_sess->data_fd); return ret; } else if (p_sess->data_use_ssl) { return ssl_read(p_sess, p_sess->p_data_ssl, p_buf, len); } else { return vsf_sysutil_read(p_sess->data_fd, p_buf, len); } } int ftp_write_data(const struct vsf_session* p_sess, const char* p_buf, unsigned int len) { if (p_sess->data_use_ssl && p_sess->ssl_slave_active) { int ret; priv_sock_send_cmd(p_sess->ssl_consumer_fd, PRIV_SOCK_DO_SSL_WRITE); priv_sock_send_buf(p_sess->ssl_consumer_fd, p_buf, len); ret = priv_sock_get_int(p_sess->ssl_consumer_fd); /* Need to do this here too because it is useless in the slave process. */ vsf_sysutil_check_pending_actions(kVSFSysUtilIO, ret, p_sess->data_fd); return ret; } else if (p_sess->data_use_ssl) { return ssl_write(p_sess->p_data_ssl, p_buf, len); } else { return vsf_sysutil_write_loop(p_sess->data_fd, p_buf, len); } } int ftp_getline(struct vsf_session* p_sess, struct mystr* p_str, char* p_buf) { if (p_sess->control_use_ssl && p_sess->ssl_slave_active) { int ret; priv_sock_send_cmd(p_sess->ssl_consumer_fd, PRIV_SOCK_GET_USER_CMD); ret = priv_sock_get_int(p_sess->ssl_consumer_fd); if (ret >= 0) { priv_sock_get_str(p_sess->ssl_consumer_fd, p_str); } return ret; } else { str_netfd_read_t p_peek = plain_peek_adapter; str_netfd_read_t p_read = plain_read_adapter; if (p_sess->control_use_ssl) { p_peek = ssl_peek_adapter; p_read = ssl_read_adapter; } return str_netfd_alloc(p_sess, p_str, '\n', p_buf, VSFTP_MAX_COMMAND_LINE, p_peek, p_read); } } static int plain_peek_adapter(struct vsf_session* p_sess, char* p_buf, unsigned int len) { (void) p_sess; return vsf_sysutil_recv_peek(VSFTP_COMMAND_FD, p_buf, len); } static int plain_read_adapter(struct vsf_session* p_sess, char* p_buf, unsigned int len) { (void) p_sess; return vsf_sysutil_read_loop(VSFTP_COMMAND_FD, p_buf, len); } static int ssl_peek_adapter(struct vsf_session* p_sess, char* p_buf, unsigned int len) { return ssl_peek(p_sess, p_sess->p_control_ssl, p_buf, len); } static int ssl_read_adapter(struct vsf_session* p_sess, char* p_buf, unsigned int len) { return ssl_read(p_sess, p_sess->p_control_ssl, p_buf, len); } vsftpd-3.0.3/opts.c0000644000175000017500000000074110754214220013155 0ustar chrischris/* * Part of Very Secure FTPd * Licence: GPL v2 * Author: Chris Evans * opts.c * * Routines to handle OPTS. */ #include "ftpcodes.h" #include "ftpcmdio.h" #include "session.h" void handle_opts(struct vsf_session* p_sess) { str_upper(&p_sess->ftp_arg_str); if (str_equal_text(&p_sess->ftp_arg_str, "UTF8 ON")) { vsf_cmdio_write(p_sess, FTP_OPTSOK, "Always in UTF8 mode."); } else { vsf_cmdio_write(p_sess, FTP_BADOPTS, "Option not understood."); } } vsftpd-3.0.3/ls.h0000644000175000017500000000374311526413052012622 0ustar chrischris#ifndef VSF_LS_H #define VSF_LS_H struct mystr; struct mystr_list; struct vsf_sysutil_dir; /* vsf_ls_populate_dir_list() * PURPOSE * Given a directory handle, populate a formatted directory entry list (/bin/ls * format). Also optionally populate a list of subdirectories. * PARAMETERS * p_list - the string list object for the result list of entries * p_subdir_list - the string list object for the result list of * subdirectories. May be 0 if client is not interested. * p_dir - the directory object to be listed * p_base_dir_str - the directory name we are listing, relative to current * p_option_str - the string of options given to the LIST/NLST command * p_filter_str - the filter string given to LIST/NLST - e.g. "*.mp3" * is_verbose - set to 1 for LIST, 0 for NLST */ void vsf_ls_populate_dir_list(struct mystr_list* p_list, struct mystr_list* p_subdir_list, struct vsf_sysutil_dir* p_dir, const struct mystr* p_base_dir_str, const struct mystr* p_option_str, const struct mystr* p_filter_str, int is_verbose); /* vsf_filename_passes_filter() * PURPOSE * Determine whether the given filename is matched by the given filter string. * The format of the filter string is a small subset of a regular expression. * Currently, just * and ? are supported. * PARAMETERS * p_filename_str - the filename to match * p_filter_str - the filter to match against * iters - pointer to a zero-seeded int which prevents the match * loop from running an excessive number of times * RETURNS * Returns 1 if there is a match, 0 otherwise. */ int vsf_filename_passes_filter(const struct mystr* p_filename_str, const struct mystr* p_filter_str, unsigned int* iters); #endif /* VSF_LS_H */ vsftpd-3.0.3/EXAMPLE/0000755000175000017500000000000010750743700013123 5ustar chrischrisvsftpd-3.0.3/EXAMPLE/INTERNET_SITE_NOINETD/0000755000175000017500000000000010750743700016357 5ustar chrischrisvsftpd-3.0.3/EXAMPLE/INTERNET_SITE_NOINETD/vsftpd.conf0000644000175000017500000000106510750743700020536 0ustar chrischris# Standalone mode listen=YES max_clients=200 max_per_ip=4 # Access rights anonymous_enable=YES local_enable=NO write_enable=NO anon_upload_enable=NO anon_mkdir_write_enable=NO anon_other_write_enable=NO # Security anon_world_readable_only=YES connect_from_port_20=YES hide_ids=YES pasv_min_port=50000 pasv_max_port=60000 # Features xferlog_enable=YES ls_recurse_enable=NO ascii_download_enable=NO async_abor_enable=YES # Performance one_process_model=YES idle_session_timeout=120 data_connection_timeout=300 accept_timeout=60 connect_timeout=60 anon_max_rate=50000 vsftpd-3.0.3/EXAMPLE/INTERNET_SITE_NOINETD/README0000644000175000017500000000377310750743700017251 0ustar chrischrisThis example shows how to run vsftpd in "standalone" mode - i.e. without needing to run an inetd of some kind (inetd, xinetd, tcpserver etc). vsftpd has supported standalone mode since v1.1.0. With the release of v1.1.3, the feature list of standalone mode has grown so that large internet sites no longer need to use an inetd. Previously, large internet sites were forced to use xinetd for the important feature of "limit number of concurrent connections from a single IP address". Unfortunately, there seem to be xinetd stability issues - various larger sites are reporting that xinetd's session counting can go wrong and incorrectly kick off users because it thinks the FTP site is full when it is not. vsftpd now natively handles maximum session counts and maximum session per IP counts. It can also do native access control via tcp_wrappers integration and even per-connect-IP configurability. To use this example config: 1) Copy the vsftpd.conf file in this directory to /etc/vsftpd.conf. 2) Start up vsftpd, e.g. vsftpd & 3) That should be it! The example vsftpd.conf is based on the vsftpd.conf from the INTERNET_SITE example. Let's look at the differences (at the top): # Standalone mode listen=YES This tells vsftpd to run in standalone mode. Do NOT try and run vsftpd from an inetd with this option set - it won't work, you may well get 500 OOPS: could not bind listening socket. max_clients=200 max_per_ip=4 The maximum number of session is 200 (new clients will get refused with a busy message). The maximum number of sessions from a single IP is 4 (the 5th connect will get refused with a suitable message). One further note on standalone mode, regarding virtual IPs. This is very easy - just run one copy of vsftpd per virtual IP (remembering to give each a separate config file on the command line). Distinguish which vsftpd is for which virtual IP with a setting like this in the vsftpd.conf: listen_address=192.168.1.2 And launch vsftpd with a specific config file like this: vsftpd /etc/vsftpd.conf.site1 & vsftpd-3.0.3/EXAMPLE/PER_IP_CONFIG/0000755000175000017500000000000010750743700015166 5ustar chrischrisvsftpd-3.0.3/EXAMPLE/PER_IP_CONFIG/hosts.allow0000644000175000017500000000040310750743700017363 0ustar chrischris# # hosts.allow This file describes the names of the hosts which are # allowed to use the local INET services, as decided # by the '/usr/sbin/tcpd' server. # vsftpd: 192.168.1.3: setenv VSFTPD_LOAD_CONF /etc/vsftpd_tcp_wrap.conf vsftpd: 192.168.1.4: DENY vsftpd-3.0.3/EXAMPLE/PER_IP_CONFIG/README0000644000175000017500000000273210750743700016052 0ustar chrischrisThis example should quickly show you the possibilites of per-IP configuration with vsftpd's tcp_wrappers integration. This is new with v1.1.3. To use this, you need vsftpd built with tcp_wrappers! This is accomplished by editing "builddefs.h" and changing #undef VSF_BUILD_TCPWRAPPERS to #define VSF_BUILD_TCPWRAPPERS And then rebuild. If you are lucky your vendor will have shipped the vsftpd binary with this already done for you. Next, to enable tcp_wrappers integration, you need this in your vsftpd.conf: tcp_wrappers=YES And you'll need a tcp_wrappers config file. An example one is supplied in this directory: hosts.allow. It lives at /etc/hosts.allow. Let's have a look at the example: vsftpd: 192.168.1.3: setenv VSFTPD_LOAD_CONF /etc/vsftpd_tcp_wrap.conf vsftpd: 192.168.1.4: DENY The first line: If a client connects from 192.168.1.3, then vsftpd will apply the vsftpd config file /etc/vsftpd_tcp_wrap.conf to the session! These settings are applied ON TOP of the default vsftpd.conf. This is obviously very powerful. You might use this to apply different access restrictions for some IPs (e.g. the ability to upload). Or you could give certain classes of IPs the ability to skip connection limits (max_clients=0). Or you could increase / decrease the bandwidth limiter for certain classes of IPs. You get the point :-) The second line: Denies the ability of 192.168.1.4 to connect. Very useful to take care of troublemakers. And now you don't need xinetd to do it - hurrah. vsftpd-3.0.3/EXAMPLE/VIRTUAL_HOSTS/0000755000175000017500000000000010750743700015231 5ustar chrischrisvsftpd-3.0.3/EXAMPLE/VIRTUAL_HOSTS/README0000644000175000017500000000416010750743700016112 0ustar chrischrisThis example shows how you might set up virtual hosts. Virtual hosting is where different clients access your machine on different IP addresses (virtual IPs) and get redirected to different ftp sites. For example, if your machine responds to two IPs - 127.0.0.1 and 127.0.0.2, you could have the two different IPs represent two totally different FTP sites. For this example, we are going to build on the "INTERNET_SITE" example. Step 1) Set up a virtual IP address. ifconfig eth0:1 192.168.1.10 up (the standard IP address is 192.168.1.2) (note - this isn't quite complete, the route for local connects hasn't been added, but it will do for now) Step 2) Create a user / location for the new virtual site. useradd -d /var/ftp_site2 ftp_site2 chown root.root /var/ftp_site2 chmod a+rx /var/ftp_site2 umask 022 mkdir /var/ftp_site2/pub echo "test" > /var/ftp_site2/pub/content Step 3) Modify the existing site to respond to the primary IP. Edit /etc/xinetd.d/vsftpd, and add the config line: bind = 192.168.1.2 Step 4) Create the new site, responding on the virtual IP. cp /etc/xinetd.d/vsftpd /etc/xinetd.d/vsftpd2 Edit vsftpd2, and change - The bind line to refer to the IP address 192.168.1.10 - Add the line server_args = /etc/vsftpd_site2.conf This launches this FTP site with a different vsftpd configuration file. cp /etc/vsftpd.conf /etc/vsftpd_site2.conf Add two lines: ftp_username=ftp_site2 ftpd_banner=This is the alternative FTP site. Step 5) Restart xinetd and test! /etc/rc.d/init.d/xinetd restart [chris@localhost vsftpd]$ ftp 192.168.1.2 Connected to 192.168.1.2 (192.168.1.2). 220 ready, dude (vsFTPd 1.1.0: beat me, break me) Name (192.168.1.2:chris): [chris@localhost vsftpd]$ [chris@localhost vsftpd]$ ftp 192.168.1.2 Connected to 192.168.1.2 (192.168.1.2). 220 ready, dude (vsFTPd 1.1.0: beat me, break me) Name (192.168.1.2:chris): 530 This FTP server is anonymous only. Login failed. ftp> quit 221 Goodbye. [chris@localhost vsftpd]$ ftp 192.168.1.10 Connected to 192.168.1.10 (192.168.1.10). 220 This is the alternative FTP site. Name (192.168.1.10:chris): 530 This FTP server is anonymous only. Login failed. ftp> vsftpd-3.0.3/EXAMPLE/INTERNET_SITE/0000755000175000017500000000000010750743700015177 5ustar chrischrisvsftpd-3.0.3/EXAMPLE/INTERNET_SITE/vsftpd.conf0000644000175000017500000000077310750743700017363 0ustar chrischris# Access rights anonymous_enable=YES local_enable=NO write_enable=NO anon_upload_enable=NO anon_mkdir_write_enable=NO anon_other_write_enable=NO # Security anon_world_readable_only=YES connect_from_port_20=YES hide_ids=YES pasv_min_port=50000 pasv_max_port=60000 # Features xferlog_enable=YES ls_recurse_enable=NO ascii_download_enable=NO async_abor_enable=YES # Performance one_process_model=YES idle_session_timeout=120 data_connection_timeout=300 accept_timeout=60 connect_timeout=60 anon_max_rate=50000 vsftpd-3.0.3/EXAMPLE/INTERNET_SITE/vsftpd.xinetd0000644000175000017500000000102510750743700017720 0ustar chrischris# vsftpd is the secure FTP server. service ftp { disable = no socket_type = stream wait = no user = root server = /usr/local/sbin/vsftpd per_source = 5 instances = 200 no_access = 192.168.1.3 banner_fail = /etc/vsftpd.busy_banner log_on_success += PID HOST DURATION log_on_failure += HOST } vsftpd-3.0.3/EXAMPLE/INTERNET_SITE/README0000644000175000017500000001027710750743700016066 0ustar chrischrisThis example shows how you might set up a (possibly large) internet facing FTP site. The emphasis will be on security and performance. We will see how by integrating vsftpd with xinetd, we get a powerful combination. Step 1) Set up your xinetd configuration file. An example xinetd configuration file "vsftpd.xinetd" is supplied. To install it: cp vsftpd.xinetd /etc/xinetd.d/vsftpd Let's look at the important content in this file and see what it does: disable = no socket_type = stream wait = no This says that the service is active, and it is using standard TCP sockets. user = root server = /usr/local/sbin/vsftpd The server program /usr/local/sbin/vsftpd is used to handle incoming FTP requests, and the program is started as root (vsftpd will of course quickly drop as much privilege as possible). NOTE! Make sure that you have the vsftpd binary installed in /usr/local/sbin (or change the file path in the xinetd file). per_source = 5 instances = 200 For security, the maximum allowed connections from a single IP address is 5. The total maximum concurrent connections is 200. no_access = 192.168.1.3 As an example of how to ban certain sites from connecting, 192.168.1.3 will be denied access. banner_fail = /etc/vsftpd.busy_banner This is the file to display to users if the connection is refused for whatever reason (too many users, IP banned). Example of how to populate it: echo "421 Server busy, please try later." > /etc/vsftpd.busy_banner log_on_success += PID HOST DURATION log_on_failure += HOST This will log the IP address of all connection attempts - successful or not, along with the time. If an FTP server is launched for the connection, it's process ID and usage duration will be logged too. If you are using RedHat like me, this log information will appear in /var/log/secure. Step 2) Set up your vsftpd configuration file. An example file is supplied. Install it like this: cp vsftpd.conf /etc Let's example the contents of the file: # Access rights anonymous_enable=YES local_enable=NO write_enable=NO anon_upload_enable=NO anon_mkdir_write_enable=NO anon_other_write_enable=NO This makes sure the FTP server is in anonymous-only mode and that all write and upload permissions are disabled. Note that most of these settings are the same as the default values anyway - but where security is concerned, it is good to be clear. # Security anon_world_readable_only=YES connect_from_port_20=YES hide_ids=YES pasv_min_port=50000 pasv_max_port=60000 These settings, in order - Make sure only world-readable files and directories are served. - Originates FTP port connections from a secure port - so users on the FTP server cannot try and fake file content. - Hide the FTP server user IDs and just display "ftp" in directory listings. This is also a performance boost. - Set a 50000-60000 port range for passive connections - may enable easier firewall setup! # Features xferlog_enable=YES ls_recurse_enable=NO ascii_download_enable=NO async_abor_enable=YES In order, - Enables recording of transfer stats to /var/log/vsftpd.log - Disables "ls -R", to prevent it being used as a DoS attack. Note - sites wanting to be copied via the "mirror" program might need to enable this. - Disables downloading in ASCII mode, to prevent it being used as a DoS attack (ASCII downloads are CPU heavy). - Enables older FTP clients to cancel in-progress transfers. # Performance one_process_model=YES idle_session_timeout=120 data_connection_timeout=300 accept_timeout=60 connect_timeout=60 anon_max_rate=50000 In order, - Activates a faster "one process per connection" model. Note! To maintain security, this feature is only available on systems with capabilities - e.g. Linux kernel 2.4. - Boots off idle users after 2 minutes. - Boots off idle downloads after 5 minutes. - Boots off hung passive connects after 1 minute. - Boots off hung active connects after 1 minute. - Limits a single client to ~50kbytes / sec download speed. Step 3) Restart xinetd. (on RedHat) /etc/rc.d/init.d/xinetd restart If you run into problems, check: 1) Your /etc/xinetd.d directory only has one FTP service. vsftpd-3.0.3/EXAMPLE/VIRTUAL_USERS_2/0000755000175000017500000000000010750743700015453 5ustar chrischrisvsftpd-3.0.3/EXAMPLE/VIRTUAL_USERS_2/README0000644000175000017500000000372310750743700016340 0ustar chrischrisThis example shows how to extend the "VIRTUAL_USERS" example to reflect a slightly more complex setup. Let's assume that we want two types of virtual user - one that can only browse and download content, and another that can upload new content as well as download existing content. To achieve this setup, we can use use of vsftpd's powerful per-user configurability (new in v1.1.0). In the previous virtual user example, we created two users - tom and fred. Let's say that we want fred to have write access to upload new files whilst tom can only download. Step 1) Activate per-user configurability. To activate this powerful vsftpd feature, add the following to /etc/vsftpd.conf: user_config_dir=/etc/vsftpd_user_conf And, create this directory: mkdir /etc/vsftpd_user_conf Step 2) Give tom the ability to read all files / directories. At the end of the last example, we noted that the virtual users can only see world-readable files and directories. We could make the /home/ftpsite directory world readable, and upload files with world-read permission. But another way of doing this is giving tom the ability to download files which are not world-readable. For the tom user, supply a config setting override for anon_world_readable_only: echo "anon_world_readable_only=NO" > /etc/vsftpd_user_conf/tom Check it out - login as tom and now "ls" will return a directory listing! Log in as fred and it won't. NOTE - restart vsftpd to pick up the config setting changes to /etc/vsftpd.conf. (Advanced users can send SIGHUP to the vsftpd listener process). Step 3) Give fred the ability to read all files / directories and create new ones but not interfere with existing files. echo "anon_world_readable_only=NO" > /etc/vsftpd_user_conf/fred echo "write_enable=YES" >> /etc/vsftpd_user_conf/fred echo "anon_upload_enable=YES" >> /etc/vsftpd_user_conf/fred Check it out - login as tom and you can't upload. Log in as fred and you can! Try and delete a file as both tom and fred - you can't. vsftpd-3.0.3/EXAMPLE/README0000644000175000017500000000145710750743700014012 0ustar chrischrisThese subdirectories contain examples of vsftpd usage. These examples are known to work on a RedHat 7.2 installation. Some of them rely on xinetd and / or a highly functional version of PAM. The examples should serve to illustrate how vsftpd becomes extremely powerful when intregration with xinetd for connection handling and PAM for authentication. Contents ======== INTERNET_SITE How you might configure vsftpd for an internet site. INTERNET_SITE_NOINETD How to use vsftpd without xinetd. PER_IP_CONFIG How to apply different settings based on the connecting IP address. VIRTUAL_HOSTS How to set up vsftpd with virtual hosting. VIRTUAL_USERS How to set up virtual users with vsftpd. VIRTUAL_USERS_2 Advanced virtual users - different access rights. vsftpd-3.0.3/EXAMPLE/VIRTUAL_USERS/0000755000175000017500000000000010750743700015232 5ustar chrischrisvsftpd-3.0.3/EXAMPLE/VIRTUAL_USERS/logins.txt0000644000175000017500000000002110750743700017257 0ustar chrischristom foo fred bar vsftpd-3.0.3/EXAMPLE/VIRTUAL_USERS/vsftpd.pam0000644000175000017500000000020110750743700017230 0ustar chrischrisauth required /lib/security/pam_userdb.so db=/etc/vsftpd_login account required /lib/security/pam_userdb.so db=/etc/vsftpd_login vsftpd-3.0.3/EXAMPLE/VIRTUAL_USERS/vsftpd.conf0000644000175000017500000000040410750743700017405 0ustar chrischrisanonymous_enable=NO local_enable=YES write_enable=NO anon_upload_enable=NO anon_mkdir_write_enable=NO anon_other_write_enable=NO chroot_local_user=YES guest_enable=YES guest_username=virtual listen=YES listen_port=10021 pasv_min_port=30000 pasv_max_port=30999 vsftpd-3.0.3/EXAMPLE/VIRTUAL_USERS/README0000644000175000017500000001175310750743700016121 0ustar chrischrisThis example shows how to set up vsftpd / PAM with "virtual users". A virtual user is a user login which does not exist as a real login on the system. Virtual users can therefore be more secure than real users, beacuse a compromised account can only use the FTP server. Virtual users are often used to serve content that should be accessible to untrusted users, but not generally accessible to the public. Step 1) Create the virtual users database. We are going to use pam_userdb to authenticate the virtual users. This needs a username / password file in "db" format - a common database format. To create a "db" format file, first create a plain text files with the usernames and password on alternating lines. See example file "logins.txt" - this specifies "tom" with password "foo" and "fred" with password "bar". Whilst logged in as root, create the actual database file like this: db_load -T -t hash -f logins.txt /etc/vsftpd_login.db (Requires the Berkeley db program installed). NOTE: Many systems have multiple versions of "db" installed, so you may need to use e.g. db3_load for correct operation. This is known to affect some Debian systems. The core issue is that pam_userdb expects its login database to be a specific db version (often db3, whereas db4 may be installed on your system). This will create /etc/vsftpd_login.db. Obviously, you may want to make sure the permissions are restricted: chmod 600 /etc/vsftpd_login.db For more information on maintaing your login database, look around for documentation on "Berkeley DB", e.g. http://www.sleepycat.com/docs/utility/index.html Step 2) Create a PAM file which uses your new database. See the example file vsftpd.pam. It contains two lines: auth required /lib/security/pam_userdb.so db=/etc/vsftpd_login account required /lib/security/pam_userdb.so db=/etc/vsftpd_login This tells PAM to authenticate users using our new database. Copy this PAM file to the PAM directory - typically /etc/pam.d/ cp vsftpd.pam /etc/pam.d/ftp (Note - if you set pam_service_name to e.g. vsftpd instead, you'll need to copy to /etc/pam.d/vsftpd). Step 3) Set up the location of the files for the virtual users. useradd -d /home/ftpsite virtual ls -ld /home/ftpsite (which should give): drwx------ 3 virtual virtual 4096 Jul 30 00:39 /home/ftpsite We have created a user called "virtual" with a home directory "/home/ftpsite". Let's add some content to this download area: cp /etc/hosts /home/ftpsite chown virtual.virtual /home/ftpsite/hosts Step 4) Create your vsftpd.conf config file. See the example in this directory. Let's go through it line by line: anonymous_enable=NO local_enable=YES This disables anonymous FTP for security, and enables non-anonymous FTP (which is what virtual users use). write_enable=NO anon_upload_enable=NO anon_mkdir_write_enable=NO anon_other_write_enable=NO These ensure that for security purposes, no write commands are allowed. chroot_local_user=YES This makes sure that the virtual user is restricted to the virtual FTP area /home/ftpsite we set up above. guest_enable=YES guest_username=virtual The guest_enable is very important - it activates virtual users! And guest_username says that all virtual users are mapped to the real user "virtual" that we set up above. This will also determine where on the filesystem the virtual users end up - the home directory of the user "virtual", /home/ftpsite. listen=YES listen_port=10021 This puts vsftpd in "standalone" mode - i.e. not running from an inetd. This means you just run the vsftpd executable and it will start up. This also makes vsftpd listen for FTP requests on the non-standard port of 10021 (FTP is usually 21). pasv_min_port=30000 pasv_max_port=30999 These put a port range on passive FTP incoming requests - very useful if you are configuring a firewall. Copy the example vsftpd.conf file to /etc: cp vsftpd.conf /etc/ Step 5) Start up vsftpd. Go to the directory with the vsftpd binary in it, and: ./vsftpd If all is well, the command will sit there. If all is not well, you will likely see some error message. Step 6) Test. Launch another shell session (or background vsftpd with CTRL-Z and then "bg"). Here is an example of an FTP session: ftp localhost 10021 Connected to localhost (127.0.0.1). 220 ready, dude (vsFTPd 1.1.0: beat me, break me) Name (localhost:chris): tom 331 Please specify the password. Password: 230 Login successful. Have fun. Remote system type is UNIX. Using binary mode to transfer files. ftp> pwd 257 "/" ftp> ls 227 Entering Passive Mode (127,0,0,1,117,135) 150 Here comes the directory listing. 226 Transfer done (but failed to open directory). ftp> size hosts 213 147 ftp> Comments: The password we gave was "foo". Do not be alarmed by the "failed to open directory". That is because the directory /home/ftpsite is not world readable (we could change this behaviour if we wanted using anon_world_readable_only=NO but maybe we want it this way for security. We can see that we have access to the "hosts" file we copied into the virtual FTP area, via the size command. vsftpd-3.0.3/ipaddrparse.h0000644000175000017500000000106610750743677014517 0ustar chrischris#ifndef VSF_IPADDRPARSE_H #define VSF_IPADDRPARSE_H struct mystr; /* Effectively doing the same sort of job as inet_pton. Since inet_pton does * a non-trivial amount of parsing, we'll do it ourselves for maximum security * and safety. */ const unsigned char* vsf_sysutil_parse_ipv6(const struct mystr* p_str); const unsigned char* vsf_sysutil_parse_ipv4(const struct mystr* p_str); const unsigned char* vsf_sysutil_parse_uchar_string_sep( const struct mystr* p_str, char sep, unsigned char* p_items, unsigned int items); #endif /* VSF_IPADDRPARSE_H */ vsftpd-3.0.3/sysdeputil.c0000644000175000017500000007655312025251374014417 0ustar chrischris/* * Part of Very Secure FTPd * Licence: GPL v2 * Author: Chris Evans * sysdeputil.c * * Highly system dependent utilities - e.g. authentication, capabilities. */ #include "sysdeputil.h" #include "str.h" #include "sysutil.h" #include "utility.h" #include "secbuf.h" #include "defs.h" #include "tunables.h" #include "builddefs.h" /* For Linux, this adds nothing :-) */ #include "port/porting_junk.h" #if (defined(__FreeBSD__) && __FreeBSD__ >= 3) #define _FILE_OFFSET_BITS 64 #define _LARGEFILE_SOURCE 1 #define _LARGEFILE64_SOURCE 1 #endif /* For INT_MAX */ #include /* For fd passing */ #include #include /* For FreeBSD */ #include #include #include #include /* Configuration.. here are the possibilities */ #undef VSF_SYSDEP_HAVE_CAPABILITIES #undef VSF_SYSDEP_HAVE_SETKEEPCAPS #undef VSF_SYSDEP_HAVE_SETPDEATHSIG #undef VSF_SYSDEP_HAVE_LINUX_SENDFILE #undef VSF_SYSDEP_HAVE_FREEBSD_SENDFILE #undef VSF_SYSDEP_HAVE_HPUX_SENDFILE #undef VSF_SYSDEP_HAVE_AIX_SENDFILE #undef VSF_SYSDEP_HAVE_SETPROCTITLE #undef VSF_SYSDEP_TRY_LINUX_SETPROCTITLE_HACK #undef VSF_SYSDEP_HAVE_HPUX_SETPROCTITLE #undef VSF_SYSDEP_HAVE_MAP_ANON #undef VSF_SYSDEP_NEED_OLD_FD_PASSING #undef VSF_SYSDEP_HAVE_LINUX_CLONE #ifdef VSF_BUILD_PAM #define VSF_SYSDEP_HAVE_PAM #endif #define VSF_SYSDEP_HAVE_SHADOW #define VSF_SYSDEP_HAVE_USERSHELL #define VSF_SYSDEP_HAVE_LIBCAP #define VSF_SYSDEP_HAVE_UTMPX #define __USE_GNU #include /* BEGIN config */ #if defined(__linux__) #include #include #define VSF_SYSDEP_HAVE_LINUX_CLONE #include #ifndef CLONE_NEWPID #define CLONE_NEWPID 0x20000000 #endif #ifndef CLONE_NEWIPC #define CLONE_NEWIPC 0x08000000 #endif #ifndef CLONE_NEWNET #define CLONE_NEWNET 0x40000000 #endif #include #include #include #endif #if defined(__linux__) && !defined(__ia64__) && !defined(__s390__) #define VSF_SYSDEP_TRY_LINUX_SETPROCTITLE_HACK #include #if defined(LINUX_VERSION_CODE) && defined(KERNEL_VERSION) #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)) #define VSF_SYSDEP_HAVE_CAPABILITIES #define VSF_SYSDEP_HAVE_LINUX_SENDFILE #ifdef PR_SET_KEEPCAPS #define VSF_SYSDEP_HAVE_SETKEEPCAPS #endif #ifdef PR_SET_PDEATHSIG #define VSF_SYSDEP_HAVE_SETPDEATHSIG #endif #endif #endif #endif #if (defined(__FreeBSD__) && __FreeBSD__ >= 3) #define VSF_SYSDEP_HAVE_FREEBSD_SENDFILE #define VSF_SYSDEP_HAVE_SETPROCTITLE #endif #if defined(__NetBSD__) #include #define VSF_SYSDEP_HAVE_SETPROCTITLE #include #if __NetBSD_Version__ >= 106070000 #define WTMPX_FILE _PATH_WTMPX #else #undef VSF_SYSDEP_HAVE_UTMPX #endif #endif #ifdef __hpux #include #ifdef SF_DISCONNECT #define VSF_SYSDEP_HAVE_HPUX_SENDFILE #endif #include #include #ifdef PSTAT_SETCMD #define VSF_SYSDEP_HAVE_HPUX_SETPROCTITLE #endif #undef VSF_SYSDEP_HAVE_UTMPX #endif #include #include #ifdef MAP_ANON #define VSF_SYSDEP_HAVE_MAP_ANON #endif #ifdef __sgi #undef VSF_SYSDEP_HAVE_USERSHELL #undef VSF_SYSDEP_HAVE_LIBCAP #endif #ifdef _AIX #undef VSF_SYSDEP_HAVE_USERSHELL #undef VSF_SYSDEP_HAVE_LIBCAP #undef VSF_SYSDEP_HAVE_UTMPX #undef VSF_SYSDEP_HAVE_PAM #undef VSF_SYSDEP_HAVE_SHADOW #undef VSF_SYSDEP_HAVE_SETPROCTITLE #define VSF_SYSDEP_HAVE_AIX_SENDFILE #define VSF_SYSDEP_TRY_LINUX_SETPROCTITLE_HACK #define VSF_SYSDEP_HAVE_MAP_ANON #endif #ifdef __osf__ #undef VSF_SYSDEP_HAVE_USERSHELL #endif #if (defined(__sgi) || defined(__hpux) || defined(__osf__)) #define VSF_SYSDEP_NEED_OLD_FD_PASSING #endif #ifdef __sun #define VSF_SYSDEP_HAVE_SOLARIS_SENDFILE #endif /* END config */ /* PAM support - we include our own dummy version if the system lacks this */ #include /* No PAM? Try getspnam() with a getpwnam() fallback */ #ifndef VSF_SYSDEP_HAVE_PAM /* This may hit our own "dummy" include and undef VSF_SYSDEP_HAVE_SHADOW */ #include #include #include #include #endif /* Prefer libcap based capabilities over raw syscall capabilities */ #include #if defined(VSF_SYSDEP_HAVE_CAPABILITIES) && !defined(VSF_SYSDEP_HAVE_LIBCAP) #include #include #include #include int capset(cap_user_header_t header, const cap_user_data_t data) { return syscall(__NR_capset, header, data); } /* Gross HACK to avoid warnings - linux headers overlap glibc headers */ #undef __NFDBITS #undef __FDMASK #endif /* VSF_SYSDEP_HAVE_CAPABILITIES */ #if defined(VSF_SYSDEP_HAVE_LINUX_SENDFILE) || \ defined(VSF_SYSDEP_HAVE_SOLARIS_SENDFILE) #include #elif defined(VSF_SYSDEP_HAVE_FREEBSD_SENDFILE) #include #include #elif defined(VSF_SYSDEP_HAVE_HPUX_SENDFILE) #include #else /* VSF_SYSDEP_HAVE_LINUX_SENDFILE */ #include #endif /* VSF_SYSDEP_HAVE_LINUX_SENDFILE */ #ifdef VSF_SYSDEP_HAVE_SETPROCTITLE #include #include #endif #ifdef VSF_SYSDEP_TRY_LINUX_SETPROCTITLE_HACK extern char** environ; static unsigned int s_proctitle_space = 0; static int s_proctitle_inited = 0; static char* s_p_proctitle = 0; #endif #ifndef VSF_SYSDEP_HAVE_MAP_ANON #include #include #include static int s_zero_fd = -1; #endif /* File private functions/variables */ static int do_sendfile(const int out_fd, const int in_fd, unsigned int num_send, filesize_t start_pos); static void vsf_sysutil_setproctitle_internal(const char* p_text); static struct mystr s_proctitle_prefix_str; /* These two aren't static to avoid OpenBSD build warnings. */ void vsf_insert_uwtmp(const struct mystr* p_user_str, const struct mystr* p_host_str); void vsf_remove_uwtmp(void); #ifndef VSF_SYSDEP_HAVE_PAM int vsf_sysdep_check_auth(struct mystr* p_user_str, const struct mystr* p_pass_str, const struct mystr* p_remote_host) { const char* p_crypted; const struct passwd* p_pwd = getpwnam(str_getbuf(p_user_str)); (void) p_remote_host; if (p_pwd == NULL) { return 0; } #ifdef VSF_SYSDEP_HAVE_USERSHELL if (tunable_check_shell) { const char* p_shell; while ((p_shell = getusershell()) != NULL) { if (!vsf_sysutil_strcmp(p_shell, p_pwd->pw_shell)) { break; } } endusershell(); if (p_shell == NULL) { return 0; } } #endif #ifdef VSF_SYSDEP_HAVE_SHADOW { const struct spwd* p_spwd = getspnam(str_getbuf(p_user_str)); if (p_spwd != NULL) { long curr_time = vsf_sysutil_get_time_sec(); int days; days = curr_time / (60 * 60 * 24); if (p_spwd->sp_expire > 0 && p_spwd->sp_expire < days) { return 0; } if (p_spwd->sp_lstchg > 0 && p_spwd->sp_max > 0 && p_spwd->sp_lstchg + p_spwd->sp_max < days) { return 0; } p_crypted = crypt(str_getbuf(p_pass_str), p_spwd->sp_pwdp); if (!vsf_sysutil_strcmp(p_crypted, p_spwd->sp_pwdp)) { return 1; } } } #endif /* VSF_SYSDEP_HAVE_SHADOW */ p_crypted = crypt(str_getbuf(p_pass_str), p_pwd->pw_passwd); if (!vsf_sysutil_strcmp(p_crypted, p_pwd->pw_passwd)) { return 1; } return 0; } #else /* VSF_SYSDEP_HAVE_PAM */ #if (defined(__sun) || defined(__hpux)) && \ !defined(LINUX_PAM) && !defined(_OPENPAM) /* Sun's PAM doesn't use const here, while Linux-PAM and OpenPAM do */ #define lo_const #else #define lo_const const #endif typedef lo_const void* pam_item_t; static pam_handle_t* s_pamh; static struct mystr s_pword_str; static int pam_conv_func(int nmsg, const struct pam_message** p_msg, struct pam_response** p_reply, void* p_addata); static void vsf_auth_shutdown(void); int vsf_sysdep_check_auth(struct mystr* p_user_str, const struct mystr* p_pass_str, const struct mystr* p_remote_host) { int retval = -1; pam_item_t item; const char* pam_user_name = 0; struct pam_conv the_conv = { &pam_conv_func, 0 }; if (s_pamh != 0) { bug("vsf_sysdep_check_auth"); } str_copy(&s_pword_str, p_pass_str); if (tunable_pam_service_name) { retval = pam_start(tunable_pam_service_name, str_getbuf(p_user_str), &the_conv, &s_pamh); } if (retval != PAM_SUCCESS) { s_pamh = 0; return 0; } #ifdef PAM_RHOST retval = pam_set_item(s_pamh, PAM_RHOST, str_getbuf(p_remote_host)); if (retval != PAM_SUCCESS) { (void) pam_end(s_pamh, retval); s_pamh = 0; return 0; } #endif #ifdef PAM_TTY retval = pam_set_item(s_pamh, PAM_TTY, "ftp"); if (retval != PAM_SUCCESS) { (void) pam_end(s_pamh, retval); s_pamh = 0; return 0; } #endif #ifdef PAM_RUSER retval = pam_set_item(s_pamh, PAM_RUSER, str_getbuf(p_user_str)); if (retval != PAM_SUCCESS) { (void) pam_end(s_pamh, retval); s_pamh = 0; return 0; } #endif retval = pam_authenticate(s_pamh, 0); if (retval != PAM_SUCCESS) { (void) pam_end(s_pamh, retval); s_pamh = 0; return 0; } #ifdef PAM_USER retval = pam_get_item(s_pamh, PAM_USER, &item); if (retval != PAM_SUCCESS) { (void) pam_end(s_pamh, retval); s_pamh = 0; return 0; } pam_user_name = item; str_alloc_text(p_user_str, pam_user_name); #endif retval = pam_acct_mgmt(s_pamh, 0); if (retval != PAM_SUCCESS) { (void) pam_end(s_pamh, retval); s_pamh = 0; return 0; } retval = pam_setcred(s_pamh, PAM_ESTABLISH_CRED); if (retval != PAM_SUCCESS) { (void) pam_end(s_pamh, retval); s_pamh = 0; return 0; } if (!tunable_session_support) { /* You're in already! */ (void) pam_end(s_pamh, retval); s_pamh = 0; return 1; } /* Must do this BEFORE opening a session for pam_limits to count us */ vsf_insert_uwtmp(p_user_str, p_remote_host); retval = pam_open_session(s_pamh, 0); if (retval != PAM_SUCCESS) { vsf_remove_uwtmp(); (void) pam_setcred(s_pamh, PAM_DELETE_CRED); (void) pam_end(s_pamh, retval); s_pamh = 0; return 0; } /* We MUST ensure the PAM session, utmp, wtmp etc. are cleaned up, however * we exit. */ vsf_sysutil_set_exit_func(vsf_auth_shutdown); /* You're in dude */ return 1; } static void vsf_auth_shutdown(void) { if (s_pamh == 0) { bug("vsf_auth_shutdown"); } (void) pam_close_session(s_pamh, 0); (void) pam_setcred(s_pamh, PAM_DELETE_CRED); (void) pam_end(s_pamh, PAM_SUCCESS); s_pamh = 0; vsf_remove_uwtmp(); } static int pam_conv_func(int nmsg, const struct pam_message** p_msg, struct pam_response** p_reply, void* p_addata) { int i; struct pam_response* p_resps = 0; (void) p_addata; if (nmsg < 0) { bug("dodgy nmsg in pam_conv_func"); } p_resps = vsf_sysutil_malloc(sizeof(struct pam_response) * nmsg); for (i=0; imsg_style) { case PAM_PROMPT_ECHO_OFF: p_resps[i].resp_retcode = PAM_SUCCESS; p_resps[i].resp = (char*) str_strdup(&s_pword_str); break; case PAM_TEXT_INFO: case PAM_ERROR_MSG: p_resps[i].resp_retcode = PAM_SUCCESS; p_resps[i].resp = 0; break; case PAM_PROMPT_ECHO_ON: default: vsf_sysutil_free(p_resps); return PAM_CONV_ERR; break; } } *p_reply = p_resps; return PAM_SUCCESS; } #endif /* VSF_SYSDEP_HAVE_PAM */ /* Capabilities support (or lack thereof) */ void vsf_sysdep_keep_capabilities(void) { if (!vsf_sysdep_has_capabilities_as_non_root()) { bug("asked to keep capabilities, but no support exists"); } #ifdef VSF_SYSDEP_HAVE_SETKEEPCAPS { int retval = prctl(PR_SET_KEEPCAPS, 1); if (vsf_sysutil_retval_is_error(retval)) { die("prctl"); } } #endif /* VSF_SYSDEP_HAVE_SETKEEPCAPS */ } #if !defined(VSF_SYSDEP_HAVE_CAPABILITIES) && !defined(VSF_SYSDEP_HAVE_LIBCAP) int vsf_sysdep_has_capabilities(void) { return 0; } int vsf_sysdep_has_capabilities_as_non_root(void) { return 0; } void vsf_sysdep_adopt_capabilities(unsigned int caps) { (void) caps; bug("asked to adopt capabilities, but no support exists"); } #else /* VSF_SYSDEP_HAVE_CAPABILITIES || VSF_SYSDEP_HAVE_LIBCAP */ static int do_checkcap(void); int vsf_sysdep_has_capabilities_as_non_root(void) { static int s_prctl_checked; static int s_runtime_prctl_works; if (!s_prctl_checked) { #ifdef VSF_SYSDEP_HAVE_SETKEEPCAPS /* Clarity: note embedded call to prctl() syscall */ if (!vsf_sysutil_retval_is_error(prctl(PR_SET_KEEPCAPS, 0))) { s_runtime_prctl_works = 1; } #endif /* VSF_SYSDEP_HAVE_SETKEEPCAPS */ s_prctl_checked = 1; } return s_runtime_prctl_works; } int vsf_sysdep_has_capabilities(void) { /* Even though compiled with capabilities, the runtime system may lack them. * Also, RH7.0 kernel headers advertise a 2.4.0 box, but on a 2.2.x kernel! */ static int s_caps_checked; static int s_runtime_has_caps; if (!s_caps_checked) { s_runtime_has_caps = do_checkcap(); s_caps_checked = 1; } return s_runtime_has_caps; } #ifndef VSF_SYSDEP_HAVE_LIBCAP static int do_checkcap(void) { /* EFAULT (EINVAL if page 0 mapped) vs. ENOSYS */ int retval = capset(0, 0); if (!vsf_sysutil_retval_is_error(retval) || vsf_sysutil_get_error() != kVSFSysUtilErrNOSYS) { return 1; } return 0; } void vsf_sysdep_adopt_capabilities(unsigned int caps) { /* n.b. yes I know I should be using libcap!! */ int retval; struct __user_cap_header_struct cap_head; struct __user_cap_data_struct cap_data; __u32 cap_mask = 0; if (!caps) { bug("asked to adopt no capabilities"); } vsf_sysutil_memclr(&cap_head, sizeof(cap_head)); vsf_sysutil_memclr(&cap_data, sizeof(cap_data)); cap_head.version = _LINUX_CAPABILITY_VERSION; cap_head.pid = 0; if (caps & kCapabilityCAP_CHOWN) { cap_mask |= (1 << CAP_CHOWN); } if (caps & kCapabilityCAP_NET_BIND_SERVICE) { cap_mask |= (1 << CAP_NET_BIND_SERVICE); } cap_data.effective = cap_data.permitted = cap_mask; cap_data.inheritable = 0; retval = capset(&cap_head, &cap_data); if (retval != 0) { die("capset"); } } #else /* VSF_SYSDEP_HAVE_LIBCAP */ static int do_checkcap(void) { cap_t current_caps = cap_get_proc(); cap_free(current_caps); if (current_caps != NULL) { return 1; } return 0; } void vsf_sysdep_adopt_capabilities(unsigned int caps) { int retval; cap_value_t cap_value; cap_t adopt_caps = cap_init(); if (caps & kCapabilityCAP_CHOWN) { cap_value = CAP_CHOWN; cap_set_flag(adopt_caps, CAP_EFFECTIVE, 1, &cap_value, CAP_SET); cap_set_flag(adopt_caps, CAP_PERMITTED, 1, &cap_value, CAP_SET); } if (caps & kCapabilityCAP_NET_BIND_SERVICE) { cap_value = CAP_NET_BIND_SERVICE; cap_set_flag(adopt_caps, CAP_EFFECTIVE, 1, &cap_value, CAP_SET); cap_set_flag(adopt_caps, CAP_PERMITTED, 1, &cap_value, CAP_SET); } retval = cap_set_proc(adopt_caps); if (retval != 0) { die("cap_set_proc"); } cap_free(adopt_caps); } #endif /* !VSF_SYSDEP_HAVE_LIBCAP */ #endif /* VSF_SYSDEP_HAVE_CAPABILITIES || VSF_SYSDEP_HAVE_LIBCAP */ int vsf_sysutil_sendfile(const int out_fd, const int in_fd, filesize_t* p_offset, filesize_t num_send, unsigned int max_chunk) { /* Grr - why is off_t signed? */ if (*p_offset < 0 || num_send < 0) { die("invalid offset or send count in vsf_sysutil_sendfile"); } if (max_chunk == 0) { max_chunk = INT_MAX; } while (num_send > 0) { int retval; unsigned int send_this_time; if (num_send > max_chunk) { send_this_time = max_chunk; } else { send_this_time = (unsigned int) num_send; } /* Keep input file position in line with sendfile() calls */ vsf_sysutil_lseek_to(in_fd, *p_offset); retval = do_sendfile(out_fd, in_fd, send_this_time, *p_offset); if (vsf_sysutil_retval_is_error(retval) || retval == 0) { return retval; } num_send -= retval; *p_offset += retval; } return 0; } static int do_sendfile(const int out_fd, const int in_fd, unsigned int num_send, filesize_t start_pos) { /* Probably should one day be shared with instance in ftpdataio.c */ static char* p_recvbuf; unsigned int total_written = 0; int retval; enum EVSFSysUtilError error; (void) start_pos; (void) error; #if defined(VSF_SYSDEP_HAVE_LINUX_SENDFILE) || \ defined(VSF_SYSDEP_HAVE_FREEBSD_SENDFILE) || \ defined(VSF_SYSDEP_HAVE_HPUX_SENDFILE) || \ defined(VSF_SYSDEP_HAVE_AIX_SENDFILE) || \ defined(VSF_SYSDEP_HAVE_SOLARIS_SENDFILE) if (tunable_use_sendfile) { static int s_sendfile_checked; static int s_runtime_sendfile_works; if (!s_sendfile_checked || s_runtime_sendfile_works) { do { #ifdef VSF_SYSDEP_HAVE_LINUX_SENDFILE retval = sendfile(out_fd, in_fd, NULL, num_send); #elif defined(VSF_SYSDEP_HAVE_FREEBSD_SENDFILE) { /* XXX - start_pos will truncate on 32-bit machines - can we * say "start from current pos"? */ off_t written = 0; retval = sendfile(in_fd, out_fd, start_pos, num_send, NULL, &written, 0); /* Translate to Linux-like retval */ if (written > 0) { retval = (int) written; } } #elif defined(VSF_SYSDEP_HAVE_SOLARIS_SENDFILE) { size_t written = 0; struct sendfilevec the_vec; vsf_sysutil_memclr(&the_vec, sizeof(the_vec)); the_vec.sfv_fd = in_fd; the_vec.sfv_off = start_pos; the_vec.sfv_len = num_send; retval = sendfilev(out_fd, &the_vec, 1, &written); /* Translate to Linux-like retval */ if (written > 0) { retval = (int) written; } } #elif defined(VSF_SYSDEP_HAVE_AIX_SENDFILE) { struct sf_parms sf_iobuf; vsf_sysutil_memclr(&sf_iobuf, sizeof(sf_iobuf)); sf_iobuf.header_data = NULL; sf_iobuf.header_length = 0; sf_iobuf.trailer_data = NULL; sf_iobuf.trailer_length = 0; sf_iobuf.file_descriptor = in_fd; sf_iobuf.file_offset = start_pos; sf_iobuf.file_bytes = num_send; retval = send_file((int*)&out_fd, &sf_iobuf, 0); if (retval >= 0) { retval = sf_iobuf.bytes_sent; } } #else /* must be VSF_SYSDEP_HAVE_HPUX_SENDFILE */ { retval = sendfile(out_fd, in_fd, start_pos, num_send, NULL, 0); } #endif /* VSF_SYSDEP_HAVE_LINUX_SENDFILE */ error = vsf_sysutil_get_error(); vsf_sysutil_check_pending_actions(kVSFSysUtilIO, retval, out_fd); } while (vsf_sysutil_retval_is_error(retval) && error == kVSFSysUtilErrINTR); if (!s_sendfile_checked) { s_sendfile_checked = 1; if (!vsf_sysutil_retval_is_error(retval) || error != kVSFSysUtilErrNOSYS) { s_runtime_sendfile_works = 1; } } if (!vsf_sysutil_retval_is_error(retval)) { return retval; } if (s_runtime_sendfile_works && error != kVSFSysUtilErrINVAL && error != kVSFSysUtilErrOPNOTSUPP) { return retval; } /* Fall thru to normal implementation. We won't check again. NOTE - * also falls through if sendfile() is OK but it returns EINVAL. For * Linux this means the file was not page cache backed. Original * complaint was trying to serve files from an NTFS filesystem! */ } } #endif /* VSF_SYSDEP_HAVE_LINUX_SENDFILE || VSF_SYSDEP_HAVE_FREEBSD_SENDFILE */ if (p_recvbuf == 0) { vsf_secbuf_alloc(&p_recvbuf, VSFTP_DATA_BUFSIZE); } while (1) { unsigned int num_read; unsigned int num_written; unsigned int num_read_this_time = VSFTP_DATA_BUFSIZE; if (num_read_this_time > num_send) { num_read_this_time = num_send; } retval = vsf_sysutil_read(in_fd, p_recvbuf, num_read_this_time); if (retval < 0) { return retval; } else if (retval == 0) { return -1; } num_read = (unsigned int) retval; retval = vsf_sysutil_write_loop(out_fd, p_recvbuf, num_read); if (retval < 0) { return retval; } num_written = (unsigned int) retval; total_written += num_written; if (num_written != num_read) { return num_written; } if (num_written > num_send) { bug("num_written bigger than num_send in do_sendfile"); } num_send -= num_written; if (num_send == 0) { /* Bingo! */ return total_written; } } } void vsf_sysutil_set_proctitle_prefix(const struct mystr* p_str) { str_copy(&s_proctitle_prefix_str, p_str); } /* This delegation is common to all setproctitle() implementations */ void vsf_sysutil_setproctitle_str(const struct mystr* p_str) { vsf_sysutil_setproctitle(str_getbuf(p_str)); } void vsf_sysutil_setproctitle(const char* p_text) { struct mystr proctitle_str = INIT_MYSTR; str_copy(&proctitle_str, &s_proctitle_prefix_str); if (!str_isempty(&proctitle_str)) { str_append_text(&proctitle_str, ": "); } str_append_text(&proctitle_str, p_text); vsf_sysutil_setproctitle_internal(str_getbuf(&proctitle_str)); str_free(&proctitle_str); } #ifdef VSF_SYSDEP_HAVE_SETPROCTITLE void vsf_sysutil_setproctitle_init(int argc, const char* argv[]) { (void) argc; (void) argv; } void vsf_sysutil_setproctitle_internal(const char* p_buf) { setproctitle("%s", p_buf); } #elif defined(VSF_SYSDEP_HAVE_HPUX_SETPROCTITLE) void vsf_sysutil_setproctitle_init(int argc, const char* argv[]) { (void) argc; (void) argv; } void vsf_sysutil_setproctitle_internal(const char* p_buf) { struct mystr proctitle_str = INIT_MYSTR; union pstun p; str_alloc_text(&proctitle_str, "vsftpd: "); str_append_text(&proctitle_str, p_buf); p.pst_command = str_getbuf(&proctitle_str); pstat(PSTAT_SETCMD, p, 0, 0, 0); str_free(&proctitle_str); } #elif defined(VSF_SYSDEP_TRY_LINUX_SETPROCTITLE_HACK) void vsf_sysutil_setproctitle_init(int argc, const char* argv[]) { int i; char** p_env = environ; if (s_proctitle_inited) { bug("vsf_sysutil_setproctitle_init called twice"); } s_proctitle_inited = 1; if (argv[0] == 0) { die("no argv[0] in vsf_sysutil_setproctitle_init"); } for (i=0; i 0) { argv[i] = 0; } } while (*p_env != 0) { s_proctitle_space += vsf_sysutil_strlen(*p_env) + 1; p_env++; } /* Oops :-) */ environ = 0; s_p_proctitle = (char*) argv[0]; vsf_sysutil_memclr(s_p_proctitle, s_proctitle_space); } void vsf_sysutil_setproctitle_internal(const char* p_buf) { struct mystr proctitle_str = INIT_MYSTR; unsigned int to_copy; if (!s_proctitle_inited) { bug("vsf_sysutil_setproctitle: not initialized"); } vsf_sysutil_memclr(s_p_proctitle, s_proctitle_space); if (s_proctitle_space < 32) { return; } str_alloc_text(&proctitle_str, "vsftpd: "); str_append_text(&proctitle_str, p_buf); to_copy = str_getlen(&proctitle_str); if (to_copy > s_proctitle_space - 1) { to_copy = s_proctitle_space - 1; } vsf_sysutil_memcpy(s_p_proctitle, str_getbuf(&proctitle_str), to_copy); str_free(&proctitle_str); s_p_proctitle[to_copy] = '\0'; } #else /* VSF_SYSDEP_HAVE_SETPROCTITLE */ void vsf_sysutil_setproctitle_init(int argc, const char* argv[]) { (void) argc; (void) argv; } void vsf_sysutil_setproctitle_internal(const char* p_buf) { (void) p_buf; } #endif /* VSF_SYSDEP_HAVE_SETPROCTITLE */ #ifdef VSF_SYSDEP_HAVE_MAP_ANON void vsf_sysutil_map_anon_pages_init(void) { } void* vsf_sysutil_map_anon_pages(unsigned int length) { char* retval = mmap(0, length, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); if (retval == MAP_FAILED) { die("mmap"); } return retval; } #else /* VSF_SYSDEP_HAVE_MAP_ANON */ void vsf_sysutil_map_anon_pages_init(void) { if (s_zero_fd != -1) { bug("vsf_sysutil_map_anon_pages_init called twice"); } s_zero_fd = open("/dev/zero", O_RDWR); if (s_zero_fd < 0) { die("could not open /dev/zero"); } } void* vsf_sysutil_map_anon_pages(unsigned int length) { char* retval = mmap(0, length, PROT_READ | PROT_WRITE, MAP_PRIVATE, s_zero_fd, 0); if (retval == MAP_FAILED) { die("mmap"); } return retval; } #endif /* VSF_SYSDEP_HAVE_MAP_ANON */ #ifndef VSF_SYSDEP_NEED_OLD_FD_PASSING void vsf_sysutil_send_fd(int sock_fd, int send_fd) { int retval; struct msghdr msg; struct cmsghdr* p_cmsg; struct iovec vec; char cmsgbuf[CMSG_SPACE(sizeof(send_fd))]; int* p_fds; char sendchar = 0; msg.msg_control = cmsgbuf; msg.msg_controllen = sizeof(cmsgbuf); p_cmsg = CMSG_FIRSTHDR(&msg); p_cmsg->cmsg_level = SOL_SOCKET; p_cmsg->cmsg_type = SCM_RIGHTS; p_cmsg->cmsg_len = CMSG_LEN(sizeof(send_fd)); p_fds = (int*)CMSG_DATA(p_cmsg); *p_fds = send_fd; msg.msg_controllen = p_cmsg->cmsg_len; msg.msg_name = NULL; msg.msg_namelen = 0; msg.msg_iov = &vec; msg.msg_iovlen = 1; msg.msg_flags = 0; /* "To pass file descriptors or credentials you need to send/read at * least on byte" (man 7 unix) */ vec.iov_base = &sendchar; vec.iov_len = sizeof(sendchar); retval = sendmsg(sock_fd, &msg, 0); if (retval != 1) { die("sendmsg"); } } int vsf_sysutil_recv_fd(const int sock_fd) { int retval; struct msghdr msg; char recvchar; struct iovec vec; int recv_fd; char cmsgbuf[CMSG_SPACE(sizeof(recv_fd))]; struct cmsghdr* p_cmsg; int* p_fd; vec.iov_base = &recvchar; vec.iov_len = sizeof(recvchar); msg.msg_name = NULL; msg.msg_namelen = 0; msg.msg_iov = &vec; msg.msg_iovlen = 1; msg.msg_control = cmsgbuf; msg.msg_controllen = sizeof(cmsgbuf); msg.msg_flags = 0; /* In case something goes wrong, set the fd to -1 before the syscall */ p_fd = (int*)CMSG_DATA(CMSG_FIRSTHDR(&msg)); *p_fd = -1; retval = recvmsg(sock_fd, &msg, 0); if (retval != 1) { die("recvmsg"); } p_cmsg = CMSG_FIRSTHDR(&msg); if (p_cmsg == NULL) { die("no passed fd"); } /* We used to verify the returned cmsg_level, cmsg_type and cmsg_len here, * but Linux 2.0 totally uselessly fails to fill these in. */ p_fd = (int*)CMSG_DATA(p_cmsg); recv_fd = *p_fd; if (recv_fd == -1) { die("no passed fd"); } return recv_fd; } #else /* !VSF_SYSDEP_NEED_OLD_FD_PASSING */ void vsf_sysutil_send_fd(int sock_fd, int send_fd) { int retval; char send_char = 0; struct msghdr msg; struct iovec vec; vec.iov_base = &send_char; vec.iov_len = 1; msg.msg_name = NULL; msg.msg_namelen = 0; msg.msg_iov = &vec; msg.msg_iovlen = 1; msg.msg_accrights = (caddr_t) &send_fd; msg.msg_accrightslen = sizeof(send_fd); retval = sendmsg(sock_fd, &msg, 0); if (retval != 1) { die("sendmsg"); } } int vsf_sysutil_recv_fd(int sock_fd) { int retval; struct msghdr msg; struct iovec vec; char recv_char; int recv_fd = -1; vec.iov_base = &recv_char; vec.iov_len = 1; msg.msg_name = NULL; msg.msg_namelen = 0; msg.msg_iov = &vec; msg.msg_iovlen = 1; msg.msg_accrights = (caddr_t) &recv_fd; msg.msg_accrightslen = sizeof(recv_fd); retval = recvmsg(sock_fd, &msg, 0); if (retval != 1) { die("recvmsg"); } if (recv_fd == -1) { die("no passed fd"); } return recv_fd; } #endif /* !VSF_SYSDEP_NEED_OLD_FD_PASSING */ #ifndef VSF_SYSDEP_HAVE_UTMPX void vsf_insert_uwtmp(const struct mystr* p_user_str, const struct mystr* p_host_str) { (void) p_user_str; (void) p_host_str; } void vsf_remove_uwtmp(void) { } #else /* !VSF_SYSDEP_HAVE_UTMPX */ /* IMHO, the pam_unix module REALLY should be doing this in its SM component */ /* Statics */ static int s_uwtmp_inserted; static struct utmpx s_utent; void vsf_insert_uwtmp(const struct mystr* p_user_str, const struct mystr* p_host_str) { if (sizeof(s_utent.ut_line) < 16) { return; } if (s_uwtmp_inserted) { bug("vsf_insert_uwtmp"); } { struct mystr line_str = INIT_MYSTR; str_alloc_text(&line_str, "vsftpd:"); str_append_ulong(&line_str, vsf_sysutil_getpid()); if (str_getlen(&line_str) >= sizeof(s_utent.ut_line)) { str_free(&line_str); return; } vsf_sysutil_strcpy(s_utent.ut_line, str_getbuf(&line_str), sizeof(s_utent.ut_line)); str_free(&line_str); } s_uwtmp_inserted = 1; s_utent.ut_type = USER_PROCESS; s_utent.ut_pid = vsf_sysutil_getpid(); vsf_sysutil_strcpy(s_utent.ut_user, str_getbuf(p_user_str), sizeof(s_utent.ut_user)); vsf_sysutil_strcpy(s_utent.ut_host, str_getbuf(p_host_str), sizeof(s_utent.ut_host)); s_utent.ut_tv.tv_sec = vsf_sysutil_get_time_sec(); setutxent(); (void) pututxline(&s_utent); endutxent(); updwtmpx(WTMPX_FILE, &s_utent); } void vsf_remove_uwtmp(void) { if (!s_uwtmp_inserted) { return; } s_uwtmp_inserted = 0; s_utent.ut_type = DEAD_PROCESS; vsf_sysutil_memclr(s_utent.ut_user, sizeof(s_utent.ut_user)); vsf_sysutil_memclr(s_utent.ut_host, sizeof(s_utent.ut_host)); s_utent.ut_tv.tv_sec = 0; setutxent(); (void) pututxline(&s_utent); endutxent(); s_utent.ut_tv.tv_sec = vsf_sysutil_get_time_sec(); updwtmpx(WTMPX_FILE, &s_utent); } #endif /* !VSF_SYSDEP_HAVE_UTMPX */ void vsf_set_die_if_parent_dies() { #ifdef VSF_SYSDEP_HAVE_SETPDEATHSIG if (prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0) != 0) { die("prctl"); } #endif } void vsf_set_term_if_parent_dies() { #ifdef VSF_SYSDEP_HAVE_SETPDEATHSIG if (prctl(PR_SET_PDEATHSIG, SIGTERM, 0, 0, 0) != 0) { die("prctl"); } #endif } int vsf_sysutil_fork_isolate_all_failok() { #ifdef VSF_SYSDEP_HAVE_LINUX_CLONE static int cloneflags_work = 1; if (cloneflags_work) { int ret = syscall(__NR_clone, CLONE_NEWPID | CLONE_NEWIPC | CLONE_NEWNET | SIGCHLD, NULL); if (ret != -1 || (errno != EINVAL && errno != EPERM)) { if (ret == 0) { vsf_sysutil_post_fork(); } return ret; } cloneflags_work = 0; } #endif return vsf_sysutil_fork_isolate_failok(); } int vsf_sysutil_fork_isolate_failok() { #ifdef VSF_SYSDEP_HAVE_LINUX_CLONE static int cloneflags_work = 1; if (cloneflags_work) { int ret = syscall(__NR_clone, CLONE_NEWPID | CLONE_NEWIPC | SIGCHLD, NULL); if (ret != -1 || (errno != EINVAL && errno != EPERM)) { if (ret == 0) { vsf_sysutil_post_fork(); } return ret; } cloneflags_work = 0; } #endif return vsf_sysutil_fork_failok(); } int vsf_sysutil_fork_newnet() { #ifdef VSF_SYSDEP_HAVE_LINUX_CLONE static int cloneflags_work = 1; if (cloneflags_work) { int ret = syscall(__NR_clone, CLONE_NEWNET | SIGCHLD, NULL); if (ret != -1 || (errno != EINVAL && errno != EPERM)) { if (ret == 0) { vsf_sysutil_post_fork(); } return ret; } cloneflags_work = 0; } #endif return vsf_sysutil_fork(); } int vsf_sysutil_getpid_nocache(void) { #ifdef VSF_SYSDEP_HAVE_LINUX_CLONE /* Need to defeat the glibc pid caching because we need to hit a raw * sys_clone() above. */ return syscall(__NR_getpid); #else return getpid(); #endif } vsftpd-3.0.3/ptracesandbox.h0000644000175000017500000002605211131033452015031 0ustar chrischris#ifndef VSF_PTRACESANDBOX_H #define VSF_PTRACESANDBOX_H /* Forward delcarations */ struct pt_sandbox; typedef int (*ptrace_sandbox_validator_t)(struct pt_sandbox*, void*); /* ptrace_sandbox_alloc() * PURPOSE * Allocates a ptrace sandbox object which is needed for the rest of the API. * RETURNS * NULL on failure, otherwise an opaque handle. * TODO * Only one per process supported at this time. */ struct pt_sandbox* ptrace_sandbox_alloc(); /* ptrace_sandbox_free() * PURPOSE * Frees the sandbox object. * PARAMETERS * p_sandbox - the sandbox handle to free */ void ptrace_sandbox_free(struct pt_sandbox* p_sandbox); /* ptrace_sandbox_launch_process() * PURPOSE * Launches a new process and attaches the sandbox to it when it stops. * PARAMETERS * p_sandbox - the sandbox handle * p_func - the function to call at the start of the new process * p_arg - an argument to pass to the function * RETURNS * -1 on failure, otherwise an id for the created process. Not necessarily a * "pid", please treat is as opaque! * TODO * Only one call to this per sandbox object is supported at this time. */ int ptrace_sandbox_launch_process(struct pt_sandbox* p_sandbox, void (*p_func)(void*), void* p_arg); /* ptrace_sandbox_run_processes() * PURPOSE * Runs sandboxed children until they exit or are killed. * PARAMETERS * p_sandbox - the sandbox handle * RETURNS * 0 on normal exit or death of processes. * -1 if any process breached the policy. */ int ptrace_sandbox_run_processes(struct pt_sandbox* p_sandbox); /* ptrace_sandbox_kill_processes() * PURPOSE * Safely kills off all sandboxed processes. * PARAMETERS * p_sandbox - the sandbox handle */ void ptrace_sandbox_kill_processes(struct pt_sandbox* p_sandbox); /* ptrace_sandbox_get_arg() * PURPOSE * Gets a syscall argument value for a process stopped in syscall entry. * PARAMETERS * p_sandbox - the sandbox handle * arg - the arg number to get (zero-based) * p_out - the result is written here * RETURNS * 0 on success; otherwise it's a failure. */ int ptrace_sandbox_get_arg(struct pt_sandbox* p_sandbox, int arg, unsigned long* p_out); /* ptrace_sandbox_get_socketcall_arg() * PURPOSE * Gets a syscall argument value for a process stopped in syscall entry, where * the system call is a socket-related one. On some architectures (e.g. i386, * socket calls are in fact multiplexed and store the arguments in a struct * in user space, hence the need for abstraction. * PARAMETERS * p_sandbox - the sandbox handle * arg - the arg number to get (zero-based) * p_out - the result is written here * RETURNS * 0 on success; otherwise it's a failure. */ int ptrace_sandbox_get_socketcall_arg(struct pt_sandbox* p_sandbox, int arg, unsigned long* p_out); /* ptrace_sandbox_get_long() * PURPOSE * Gets a long from the address space of the process stopped in syscall entry. * PARAMETERS * p_sandbox - the sandbox handle * ptr - the address to read the long from * p_out - the result is written here * RETURNS * 0 on success; otherwise it's a failure. */ int ptrace_sandbox_get_long(struct pt_sandbox* p_sandbox, unsigned long ptr, unsigned long* p_out); /* ptrace_sandbox_get_buf() * PURPOSE * Gets a piece of memory from the address space of the process stopped in * syscall entry. * PARAMETERS * p_sandbox - the sandbox handle * ptr - the address to read the buffer from * len - the length of the buffer * p_buf - the result is written here * RETURNS * 0 on success; otherwise it's a failure. */ int ptrace_sandbox_get_buf(struct pt_sandbox* p_sandbox, unsigned long ptr, unsigned long len, void* p_buf); /* ptrace_sandbox_attach_point() * PURPOSE * Used by the sandbox child code to stop and indicate it is ready to be * attached to. * NOTES * In the event of error trying to stop, the process is forcibly killed as a * security measure. */ void ptrace_sandbox_attach_point(void); /* POLICY EDIT: permits exit() and exit_group() */ void ptrace_sandbox_permit_exit(struct pt_sandbox* p_sandbox); /* POLICY EDIT: permits read() */ void ptrace_sandbox_permit_read(struct pt_sandbox* p_sandbox); /* POLICY EDIT: permits write() */ void ptrace_sandbox_permit_write(struct pt_sandbox* p_sandbox); /* POLICY EDIT: permits sigaction() and rt_sigaction() */ void ptrace_sandbox_permit_sigaction(struct pt_sandbox* p_sandbox); /* POLICY EDIT: permits alarm() */ void ptrace_sandbox_permit_alarm(struct pt_sandbox* p_sandbox); /* POLICY EDIT: permits time() and gettimeofday() */ void ptrace_sandbox_permit_query_time(struct pt_sandbox* p_sandbox); /* POLICY EDIT: permits mmap2() (but not the MAP_SHARED flag) */ void ptrace_sandbox_permit_mmap(struct pt_sandbox* p_sandbox); /* POLICY EDIT: permits mprotect() */ void ptrace_sandbox_permit_mprotect(struct pt_sandbox* p_sandbox); /* POLICY EDIT: permits stat(), stat64(), lstat(), lstat64() */ void ptrace_sandbox_permit_file_stats(struct pt_sandbox* p_sandbox); /* POLICY EDIT: permits fstat(), fstat64() */ void ptrace_sandbox_permit_fd_stats(struct pt_sandbox* p_sandbox); /* POLICY EDIT: permits getcwd() */ void ptrace_sandbox_permit_getcwd(struct pt_sandbox* p_sandbox); /* POLICY EDIT: permits chdir() */ void ptrace_sandbox_permit_chdir(struct pt_sandbox* p_sandbox); /* POLICY EDIT: permits umask() */ void ptrace_sandbox_permit_umask(struct pt_sandbox* p_sandbox); /* POLICY EDIT: permits open(), except O_ASYNC and O_DIRECT. Only O_RDONLY * allowed unless writeable is 1 */ void ptrace_sandbox_permit_open(struct pt_sandbox* p_sandbox, int writeable); /* POLICY EDIT: permits close() */ void ptrace_sandbox_permit_close(struct pt_sandbox* p_sandbox); /* POLICY EDIT: permits getdents(), getdents64() */ void ptrace_sandbox_permit_getdents(struct pt_sandbox* p_sandbox); /* POLICY EDIT: permits fcntl(), fcntl64() for file locking, safe F_SETFL flag * setting (no O_ASYNC, O_DIRECT), F_SETOWN for your own pid and F_SETFD. */ void ptrace_sandbox_permit_fcntl(struct pt_sandbox* p_sandbox); /* POLICY EDIT: permits sendfile(), sendfile64() */ void ptrace_sandbox_permit_sendfile(struct pt_sandbox* p_sandbox); /* POLICY EDIT: permits lseek(), llseek() */ void ptrace_sandbox_permit_seek(struct pt_sandbox* p_sandbox); /* POLICY EDIT: permits select(), newselect() */ void ptrace_sandbox_permit_select(struct pt_sandbox* p_sandbox); /* POLICY EDIT: permits unlink() */ void ptrace_sandbox_permit_unlink(struct pt_sandbox* p_sandbox); /* POLICY EDIT: permits mkdir() */ void ptrace_sandbox_permit_mkdir(struct pt_sandbox* p_sandbox); /* POLICY EDIT: permits rmdir() */ void ptrace_sandbox_permit_rmdir(struct pt_sandbox* p_sandbox); /* POLICY EDIT: permits rename() */ void ptrace_sandbox_permit_rename(struct pt_sandbox* p_sandbox); /* POLICY EDIT: permits utime(), utimes() */ void ptrace_sandbox_permit_utime(struct pt_sandbox* p_sandbox); /* POLICY EDIT: permits sigreturn() */ void ptrace_sandbox_permit_sigreturn(struct pt_sandbox* p_sandbox); /* POLICY EDIT: permits recv() */ void ptrace_sandbox_permit_recv(struct pt_sandbox* p_sandbox); /* POLICY EDIT: permits readlink() */ void ptrace_sandbox_permit_readlink(struct pt_sandbox* p_sandbox); /* POLICY EDIT: permits brk() */ void ptrace_sandbox_permit_brk(struct pt_sandbox* p_sandbox); /* POLICY EDIT: permits nanosleep() */ void ptrace_sandbox_permit_sleep(struct pt_sandbox* p_sandbox); /* POLICY EDIT: permits fchmod() */ void ptrace_sandbox_permit_fchmod(struct pt_sandbox* p_sandbox); /* POLICY EDIT: permits chmod() */ void ptrace_sandbox_permit_chmod(struct pt_sandbox* p_sandbox); /* POLICY EDIT: permits fchown(), fchown32() */ void ptrace_sandbox_permit_fchown(struct pt_sandbox* p_sandbox); /* POLICY EDIT: permits mremap() */ void ptrace_sandbox_permit_mremap(struct pt_sandbox* p_sandbox); /* POLICY EDIT: permits ftruncate(), ftruncate64() */ void ptrace_sandbox_permit_ftruncate(struct pt_sandbox* p_sandbox); /* POLICY EDIT: permits socket() */ void ptrace_sandbox_permit_socket(struct pt_sandbox* p_sandbox); /* POLICY EDIT: set validator for socket() */ void ptrace_sandbox_set_socket_validator(struct pt_sandbox* p_sandbox, ptrace_sandbox_validator_t val, void* p_arg); /* POLICY EDIT: permits bind() */ void ptrace_sandbox_permit_bind(struct pt_sandbox* p_sandbox); /* POLICY EDIT: set validator for bind() */ void ptrace_sandbox_set_bind_validator(struct pt_sandbox* p_sandbox, ptrace_sandbox_validator_t val, void* p_arg); /* POLICY EDIT: permits connect() */ void ptrace_sandbox_permit_connect(struct pt_sandbox* p_sandbox); /* POLICY EDIT: set validator for connect() */ void ptrace_sandbox_set_connect_validator(struct pt_sandbox* p_sandbox, ptrace_sandbox_validator_t val, void* p_arg); /* POLICY EDIT: permits listen() */ void ptrace_sandbox_permit_listen(struct pt_sandbox* p_sandbox); /* POLICY EDIT: permits accept() */ void ptrace_sandbox_permit_accept(struct pt_sandbox* p_sandbox); /* POLICY EDIT: permits setsockopt() */ void ptrace_sandbox_permit_setsockopt(struct pt_sandbox* p_sandbox); /* POLICY EDIT: set validator for setsockopt() */ void ptrace_sandbox_set_setsockopt_validator(struct pt_sandbox* p_sandbox, ptrace_sandbox_validator_t val, void* p_arg); /* POLICY EDIT: permits getsockopt() */ void ptrace_sandbox_permit_getsockopt(struct pt_sandbox* p_sandbox); /* POLICY EDIT: set validator for getsockopt() */ void ptrace_sandbox_set_getsockopt_validator(struct pt_sandbox* p_sandbox, ptrace_sandbox_validator_t val, void* p_arg); /* POLICY EDIT: permits shutdown() */ void ptrace_sandbox_permit_shutdown(struct pt_sandbox* p_sandbox); /* The traced process is unexpectedly dead; probably an external SIGKILL */ #define PTRACE_SANDBOX_ERR_DEAD -1 /* An unexpected error from ptrace() */ #define PTRACE_SANDBOX_ERR_PTRACE -2 /* An unexpected error from waitpid() */ #define PTRACE_SANDBOX_ERR_WAITPID -3 /* An unexpected waitpid() status was returned */ #define PTRACE_SANDBOX_ERR_WAIT_STATUS -4 /* A syscall not in the policy was attempted */ #define PTRACE_SANDBOX_ERR_POLICY_SYSCALL -5 /* A "bad" syscall was attemped: out-of-bounds, 64-bit in a 32-bit child etc. */ #define PTRACE_SANDBOX_ERR_BAD_SYSCALL -6 /* Bad arguments to a generally accepted syscall */ #define PTRACE_SANDBOX_ERR_POLICY_ARGS -7 /* Abuse of our API */ #define PTRACE_SANDBOX_ERR_API_ABUSE_STOPIT -8 #endif /* VSF_PTRACESANDBOX_H */ vsftpd-3.0.3/ftpcmdio.h0000644000175000017500000000636311736500162014014 0ustar chrischris#ifndef VSF_FTPCMDIO_H #define VSF_FTPCMDIO_H struct mystr; struct vsf_session; /* vsf_cmdio_sock_setup() * PURPOSE * Initialise a few socket settings (keepalive, nonagle, etc). on the FTP * control connection. */ void vsf_cmdio_sock_setup(void); /* vsf_cmdio_write() * PURPOSE * Write a response to the FTP control connection. * PARAMETERS * p_sess - the current session object * status - the status code to report * p_text - the text to report */ void vsf_cmdio_write(struct vsf_session* p_sess, int status, const char* p_text); /* vsf_cmdio_write_hyphen() * PURPOSE * Write a response to the FTP control connection, with a hyphen '-' * continuation indicator. * PARAMETERS * p_sess - the current session object * status - the status code to report * p_text - the text to report */ void vsf_cmdio_write_hyphen(struct vsf_session* p_sess, int status, const char* p_text); /* vsf_cmdio_write_raw() * PURPOSE * Write a raw response to the FTP control connection. A status code is * not prepended, and it is also the client's responsibility to include * newline characters if required. * PARAMETERS * p_sess - the current session object * p_text - the text to report */ void vsf_cmdio_write_raw(struct vsf_session* p_sess, const char* p_text); /* vsf_cmdio_write_exit() * PURPOSE * The same as vsf_cmdio_write(), and then the calling process is exited. The * write is _guaranteed_ to not block (ditching output if neccessary). */ void vsf_cmdio_write_exit(struct vsf_session* p_sess, int status, const char* p_text, int exit_val); /* vsf_cmdio_write_str() * PURPOSE * The same as vsf_cmdio_write(), apart from the text is specified as a * string buffer object "p_str". */ void vsf_cmdio_write_str(struct vsf_session* p_sess, int status, const struct mystr* p_str); /* vsf_cmdio_write_str_hyphen() * PURPOSE * The same as vsf_cmdio_write_str(), apart from the response line is * output with the continuation indicator '-' between the response code and * the response text. This indicates there are more lines of response. */ void vsf_cmdio_write_str_hyphen(struct vsf_session* p_sess, int status, const struct mystr* p_str); /* vsf_cmdio_set_alarm() * PURPOSE * Activate the control connection inactivity timeout. This is explicitly * exposed in the API so that we can play it safe, and activate the alarm * before _any_ potentially blocking calls. * PARAMETERS * p_sess - The current session object */ void vsf_cmdio_set_alarm(struct vsf_session* p_sess); /* vsf_cmdio_get_cmd_and_arg() * PURPOSE * Read an FTP command (and optional argument) from the FTP control connection. * PARAMETERS * p_sess - The current session object * p_cmd_str - Where to put the FTP command string (may be empty) * p_arg_str - Where to put the FTP argument string (may be empty) * set_alarm - If true, the control connection inactivity monitor is used */ void vsf_cmdio_get_cmd_and_arg(struct vsf_session* p_sess, struct mystr* p_cmd_str, struct mystr* p_arg_str, int set_alarm); #endif /* VSF_FTPCMDIO_H */ vsftpd-3.0.3/README0000644000175000017500000000252112553270376012717 0ustar chrischrisThis is vsftpd, version 3.0.3 Author: Chris Evans Contact: scarybeasts@gmail.com Website: http://vsftpd.beasts.org/ - All options are documented in the vsftpd.conf.5 manual page. - See the FAQ file for solutions to frequently asked questions. - Visit http://vsftpd.beasts.org/ for vsftpd news and releases. What is this? ============= vsftpd is an FTP server, or daemon. The "vs" stands for Very Secure. Obviously this is not a guarantee, but a reflection that I have written the entire codebase with security in mind, and carefully designed the program to be resilient to attack. Recent evidence shows that vsftpd is also extremely fast and scalable. vsftpd has achieved ~4000 concurrent users on a single machine, in a production environment. vsftpd is now a proven stable solution. Of particular note, RedHat used vsftpd to enable ftp.redhat.com to support 15,000 concurrent users across their server pool. This extreme load was generated by the release of RedHat 7.2 to the world. vsftpd now powers some of the largest and most prestigious sites on the internet. Installation ============ Please see the INSTALL file. Configuration ============= All configuration options are documented in the manual page vsftpd.conf.5. Various example configurations are discussed in the EXAMPLE directory. Frequently asked questions are tackled in the FAQ file. vsftpd-3.0.3/ftpdataio.c0000644000175000017500000004431612553761271014165 0ustar chrischris/* * Part of Very Secure FTPd * Licence: GPL v2 * Author: Chris Evans * ftpdataio.c * * Code to handle FTP data connections. This includes both PORT (server * connects) and PASV (client connects) modes of data transfer. This * includes sends and receives, files and directories. */ #include "ftpdataio.h" #include "session.h" #include "ftpcmdio.h" #include "ftpcodes.h" #include "utility.h" #include "tunables.h" #include "defs.h" #include "str.h" #include "strlist.h" #include "sysutil.h" #include "logging.h" #include "secbuf.h" #include "sysstr.h" #include "sysdeputil.h" #include "ascii.h" #include "oneprocess.h" #include "twoprocess.h" #include "ls.h" #include "ssl.h" #include "readwrite.h" #include "privsock.h" static void init_data_sock_params(struct vsf_session* p_sess, int sock_fd); static filesize_t calc_num_send(int file_fd, filesize_t init_offset); static struct vsf_transfer_ret do_file_send_sendfile( struct vsf_session* p_sess, int net_fd, int file_fd, filesize_t curr_file_offset, filesize_t bytes_to_send); static struct vsf_transfer_ret do_file_send_rwloop( struct vsf_session* p_sess, int file_fd, int is_ascii); static struct vsf_transfer_ret do_file_recv( struct vsf_session* p_sess, int file_fd, int is_ascii); static void handle_sigalrm(void* p_private); static void start_data_alarm(struct vsf_session* p_sess); static void handle_io(int retval, int fd, void* p_private); static int transfer_dir_internal( struct vsf_session* p_sess, int is_control, struct vsf_sysutil_dir* p_dir, const struct mystr* p_base_dir_str, const struct mystr* p_option_str, const struct mystr* p_filter_str, int is_verbose); static int write_dir_list(struct vsf_session* p_sess, struct mystr_list* p_dir_list, enum EVSFRWTarget target); static unsigned int get_chunk_size(); int vsf_ftpdataio_dispose_transfer_fd(struct vsf_session* p_sess) { int dispose_ret = 1; int retval; if (p_sess->data_fd == -1) { bug("no data descriptor in vsf_ftpdataio_dispose_transfer_fd"); } vsf_sysutil_uninstall_io_handler(); if (p_sess->data_use_ssl && p_sess->ssl_slave_active) { char result; start_data_alarm(p_sess); priv_sock_send_cmd(p_sess->ssl_consumer_fd, PRIV_SOCK_DO_SSL_CLOSE); result = priv_sock_get_result(p_sess->ssl_consumer_fd); if (result != PRIV_SOCK_RESULT_OK) { dispose_ret = 0; } } else if (p_sess->p_data_ssl) { start_data_alarm(p_sess); dispose_ret = ssl_data_close(p_sess); } if (!p_sess->abor_received && !p_sess->data_timeout && dispose_ret == 1) { /* If we didn't get a failure, linger on the close() in order to get more * accurate transfer times. */ start_data_alarm(p_sess); vsf_sysutil_activate_linger(p_sess->data_fd); } /* This close() blocks because we set SO_LINGER */ retval = vsf_sysutil_close_failok(p_sess->data_fd); if (vsf_sysutil_retval_is_error(retval)) { /* Do it again without blocking. */ vsf_sysutil_deactivate_linger_failok(p_sess->data_fd); (void) vsf_sysutil_close_failok(p_sess->data_fd); } p_sess->data_fd = -1; if (tunable_data_connection_timeout > 0) { vsf_sysutil_clear_alarm(); } if (p_sess->abor_received || p_sess->data_timeout) { dispose_ret = 0; } return dispose_ret; } int vsf_ftpdataio_get_pasv_fd(struct vsf_session* p_sess) { int remote_fd; if (tunable_one_process_model) { remote_fd = vsf_one_process_get_pasv_fd(p_sess); } else { remote_fd = vsf_two_process_get_pasv_fd(p_sess); } /* Yes, yes, hardcoded bad I know. */ if (remote_fd == -1) { vsf_cmdio_write(p_sess, FTP_BADSENDCONN, "Failed to establish connection."); return remote_fd; } else if (remote_fd == -2) { vsf_cmdio_write(p_sess, FTP_BADSENDCONN, "Security: Bad IP connecting."); return -1; } init_data_sock_params(p_sess, remote_fd); return remote_fd; } int vsf_ftpdataio_get_port_fd(struct vsf_session* p_sess) { int remote_fd; if (tunable_one_process_model || tunable_port_promiscuous) { remote_fd = vsf_one_process_get_priv_data_sock(p_sess); } else { remote_fd = vsf_two_process_get_priv_data_sock(p_sess); } if (vsf_sysutil_retval_is_error(remote_fd)) { vsf_cmdio_write(p_sess, FTP_BADSENDCONN, "Failed to establish connection."); return -1; } init_data_sock_params(p_sess, remote_fd); return remote_fd; } int vsf_ftpdataio_post_mark_connect(struct vsf_session* p_sess) { int ret = 0; if (!p_sess->data_use_ssl) { return 1; } if (!p_sess->ssl_slave_active) { ret = ssl_accept(p_sess, p_sess->data_fd); } else { int sock_ret; priv_sock_send_cmd(p_sess->ssl_consumer_fd, PRIV_SOCK_DO_SSL_HANDSHAKE); priv_sock_send_fd(p_sess->ssl_consumer_fd, p_sess->data_fd); sock_ret = priv_sock_get_result(p_sess->ssl_consumer_fd); if (sock_ret == PRIV_SOCK_RESULT_OK) { ret = 1; } } if (ret != 1) { if (tunable_require_ssl_reuse) { vsf_cmdio_write_exit(p_sess, FTP_DATATLSBAD, "SSL connection failed: session reuse required", 1); } else { vsf_cmdio_write(p_sess, FTP_DATATLSBAD, "SSL connection failed"); } } return ret; } static void handle_sigalrm(void* p_private) { struct vsf_session* p_sess = (struct vsf_session*) p_private; if (!p_sess->data_progress) { p_sess->data_timeout = 1; vsf_sysutil_shutdown_failok(p_sess->data_fd); vsf_sysutil_shutdown_read_failok(VSFTP_COMMAND_FD); vsf_sysutil_activate_noblock(VSFTP_COMMAND_FD); } else { p_sess->data_progress = 0; start_data_alarm(p_sess); } } void start_data_alarm(struct vsf_session* p_sess) { if (tunable_data_connection_timeout > 0) { vsf_sysutil_install_sighandler(kVSFSysUtilSigALRM, handle_sigalrm, p_sess, 1); vsf_sysutil_set_alarm(tunable_data_connection_timeout); } else if (tunable_idle_session_timeout > 0) { vsf_sysutil_clear_alarm(); } } static void init_data_sock_params(struct vsf_session* p_sess, int sock_fd) { if (p_sess->data_fd != -1) { bug("data descriptor still present in init_data_sock_params"); } p_sess->data_fd = sock_fd; p_sess->data_progress = 0; vsf_sysutil_activate_keepalive(sock_fd); /* And in the vague hope it might help... */ vsf_sysutil_set_iptos_throughput(sock_fd); /* Start the timeout monitor */ vsf_sysutil_install_io_handler(handle_io, p_sess); start_data_alarm(p_sess); } static void handle_io(int retval, int fd, void* p_private) { long curr_sec; long curr_usec; unsigned int bw_rate; double elapsed; double pause_time; double rate_ratio; struct vsf_session* p_sess = (struct vsf_session*) p_private; if (p_sess->data_fd != fd || vsf_sysutil_retval_is_error(retval) || retval == 0) { return; } /* Note that the session hasn't stalled, i.e. don't time it out */ p_sess->data_progress = 1; /* Apply bandwidth quotas via a little pause, if necessary */ if (p_sess->bw_rate_max == 0) { return; } /* Calculate bandwidth rate */ curr_sec = vsf_sysutil_get_time_sec(); curr_usec = vsf_sysutil_get_time_usec(); elapsed = (double) (curr_sec - p_sess->bw_send_start_sec); elapsed += (double) (curr_usec - p_sess->bw_send_start_usec) / (double) 1000000; if (elapsed <= (double) 0) { elapsed = (double) 0.01; } bw_rate = (unsigned int) ((double) retval / elapsed); if (bw_rate <= p_sess->bw_rate_max) { p_sess->bw_send_start_sec = curr_sec; p_sess->bw_send_start_usec = curr_usec; return; } /* Tut! Rate exceeded, calculate a pause to bring things back into line */ rate_ratio = (double) bw_rate / (double) p_sess->bw_rate_max; pause_time = (rate_ratio - (double) 1) * elapsed; vsf_sysutil_sleep(pause_time); p_sess->bw_send_start_sec = vsf_sysutil_get_time_sec(); p_sess->bw_send_start_usec = vsf_sysutil_get_time_usec(); } int vsf_ftpdataio_transfer_dir(struct vsf_session* p_sess, int is_control, struct vsf_sysutil_dir* p_dir, const struct mystr* p_base_dir_str, const struct mystr* p_option_str, const struct mystr* p_filter_str, int is_verbose) { return transfer_dir_internal(p_sess, is_control, p_dir, p_base_dir_str, p_option_str, p_filter_str, is_verbose); } static int transfer_dir_internal(struct vsf_session* p_sess, int is_control, struct vsf_sysutil_dir* p_dir, const struct mystr* p_base_dir_str, const struct mystr* p_option_str, const struct mystr* p_filter_str, int is_verbose) { struct mystr_list dir_list = INIT_STRLIST; struct mystr_list subdir_list = INIT_STRLIST; struct mystr dir_prefix_str = INIT_MYSTR; struct mystr_list* p_subdir_list = 0; struct str_locate_result loc_result = str_locate_char(p_option_str, 'R'); int failed = 0; enum EVSFRWTarget target = kVSFRWData; if (is_control) { target = kVSFRWControl; } if (loc_result.found && tunable_ls_recurse_enable) { p_subdir_list = &subdir_list; } vsf_ls_populate_dir_list(&dir_list, p_subdir_list, p_dir, p_base_dir_str, p_option_str, p_filter_str, is_verbose); if (p_subdir_list) { int retval; str_copy(&dir_prefix_str, p_base_dir_str); str_append_text(&dir_prefix_str, ":\r\n"); retval = ftp_write_str(p_sess, &dir_prefix_str, target); if (retval != 0) { failed = 1; } } if (!failed) { failed = write_dir_list(p_sess, &dir_list, target); } /* Recurse into the subdirectories if required... */ if (!failed) { struct mystr sub_str = INIT_MYSTR; unsigned int num_subdirs = str_list_get_length(&subdir_list); unsigned int subdir_index; for (subdir_index = 0; subdir_index < num_subdirs; subdir_index++) { int retval; struct vsf_sysutil_dir* p_subdir; const struct mystr* p_subdir_str = str_list_get_pstr(&subdir_list, subdir_index); if (str_equal_text(p_subdir_str, ".") || str_equal_text(p_subdir_str, "..")) { continue; } str_copy(&sub_str, p_base_dir_str); str_append_char(&sub_str, '/'); str_append_str(&sub_str, p_subdir_str); p_subdir = str_opendir(&sub_str); if (p_subdir == 0) { /* Unreadable, gone missing, etc. - no matter */ continue; } str_alloc_text(&dir_prefix_str, "\r\n"); retval = ftp_write_str(p_sess, &dir_prefix_str, target); if (retval != 0) { failed = 1; vsf_sysutil_closedir(p_subdir); break; } retval = transfer_dir_internal(p_sess, is_control, p_subdir, &sub_str, p_option_str, p_filter_str, is_verbose); vsf_sysutil_closedir(p_subdir); if (retval != 0) { failed = 1; break; } } str_free(&sub_str); } str_list_free(&dir_list); str_list_free(&subdir_list); str_free(&dir_prefix_str); if (!failed) { return 0; } else { return -1; } } /* XXX - really, this should be refactored into a "buffered writer" object */ static int write_dir_list(struct vsf_session* p_sess, struct mystr_list* p_dir_list, enum EVSFRWTarget target) { /* This function writes out a list of strings to the client, over the * data socket. We now coalesce the strings into fewer write() syscalls, * which saved 33% CPU time writing a large directory. */ int retval = 0; unsigned int dir_index_max = str_list_get_length(p_dir_list); unsigned int dir_index; struct mystr buf_str = INIT_MYSTR; str_reserve(&buf_str, VSFTP_DIR_BUFSIZE); for (dir_index = 0; dir_index < dir_index_max; dir_index++) { str_append_str(&buf_str, str_list_get_pstr(p_dir_list, dir_index)); if (dir_index == dir_index_max - 1 || str_getlen(&buf_str) + str_getlen(str_list_get_pstr(p_dir_list, dir_index + 1)) > VSFTP_DIR_BUFSIZE) { /* Writeout needed - we're either at the end, or we filled the buffer */ int writeret = ftp_write_str(p_sess, &buf_str, target); if (writeret != 0) { retval = 1; break; } str_empty(&buf_str); } } str_free(&buf_str); return retval; } struct vsf_transfer_ret vsf_ftpdataio_transfer_file(struct vsf_session* p_sess, int remote_fd, int file_fd, int is_recv, int is_ascii) { if (!is_recv) { if (is_ascii || p_sess->data_use_ssl) { return do_file_send_rwloop(p_sess, file_fd, is_ascii); } else { filesize_t curr_offset = vsf_sysutil_get_file_offset(file_fd); filesize_t num_send = calc_num_send(file_fd, curr_offset); return do_file_send_sendfile( p_sess, remote_fd, file_fd, curr_offset, num_send); } } else { return do_file_recv(p_sess, file_fd, is_ascii); } } static struct vsf_transfer_ret do_file_send_rwloop(struct vsf_session* p_sess, int file_fd, int is_ascii) { static char* p_readbuf; static char* p_asciibuf; struct vsf_transfer_ret ret_struct = { 0, 0 }; unsigned int chunk_size = get_chunk_size(); char* p_writefrom_buf; int prev_cr = 0; if (p_readbuf == 0) { vsf_secbuf_alloc(&p_readbuf, VSFTP_DATA_BUFSIZE); } if (is_ascii) { if (p_asciibuf == 0) { /* NOTE!! * 2 factor because we can double the data by doing our ASCII * linefeed mangling */ vsf_secbuf_alloc(&p_asciibuf, VSFTP_DATA_BUFSIZE * 2); } p_writefrom_buf = p_asciibuf; } else { p_writefrom_buf = p_readbuf; } while (1) { unsigned int num_to_write; int retval = vsf_sysutil_read(file_fd, p_readbuf, chunk_size); if (vsf_sysutil_retval_is_error(retval)) { ret_struct.retval = -1; return ret_struct; } else if (retval == 0) { /* Success - cool */ return ret_struct; } if (is_ascii) { struct bin_to_ascii_ret ret = vsf_ascii_bin_to_ascii(p_readbuf, p_asciibuf, (unsigned int) retval, prev_cr); num_to_write = ret.stored; prev_cr = ret.last_was_cr; } else { num_to_write = (unsigned int) retval; } retval = ftp_write_data(p_sess, p_writefrom_buf, num_to_write); if (!vsf_sysutil_retval_is_error(retval)) { ret_struct.transferred += (unsigned int) retval; } if (vsf_sysutil_retval_is_error(retval) || (unsigned int) retval != num_to_write) { ret_struct.retval = -2; return ret_struct; } } } static struct vsf_transfer_ret do_file_send_sendfile(struct vsf_session* p_sess, int net_fd, int file_fd, filesize_t curr_file_offset, filesize_t bytes_to_send) { int retval; unsigned int chunk_size = 0; struct vsf_transfer_ret ret_struct = { 0, 0 }; filesize_t init_file_offset = curr_file_offset; filesize_t bytes_sent; if (p_sess->bw_rate_max) { chunk_size = get_chunk_size(); } /* Just because I can ;-) */ retval = vsf_sysutil_sendfile(net_fd, file_fd, &curr_file_offset, bytes_to_send, chunk_size); bytes_sent = curr_file_offset - init_file_offset; ret_struct.transferred = bytes_sent; if (vsf_sysutil_retval_is_error(retval)) { ret_struct.retval = -2; return ret_struct; } else if (bytes_sent != bytes_to_send) { ret_struct.retval = -2; return ret_struct; } return ret_struct; } static filesize_t calc_num_send(int file_fd, filesize_t init_offset) { static struct vsf_sysutil_statbuf* s_p_statbuf; filesize_t bytes_to_send; /* Work out how many bytes to send based on file size minus current offset */ vsf_sysutil_fstat(file_fd, &s_p_statbuf); bytes_to_send = vsf_sysutil_statbuf_get_size(s_p_statbuf); if (init_offset < 0 || bytes_to_send < 0) { die("calc_num_send: negative file offset or send count"); } /* Don't underflow if some bonehead sets a REST greater than the file size */ if (init_offset > bytes_to_send) { bytes_to_send = 0; } else { bytes_to_send -= init_offset; } return bytes_to_send; } static struct vsf_transfer_ret do_file_recv(struct vsf_session* p_sess, int file_fd, int is_ascii) { static char* p_recvbuf; unsigned int num_to_write; struct vsf_transfer_ret ret_struct = { 0, 0 }; unsigned int chunk_size = get_chunk_size(); int prev_cr = 0; if (p_recvbuf == 0) { /* Now that we do ASCII conversion properly, the plus one is to cater for * the fact we may need to stick a '\r' at the front of the buffer if the * last buffer fragment eneded in a '\r' and the current buffer fragment * does not start with a '\n'. */ vsf_secbuf_alloc(&p_recvbuf, VSFTP_DATA_BUFSIZE + 1); } while (1) { const char* p_writebuf = p_recvbuf + 1; int retval = ftp_read_data(p_sess, p_recvbuf + 1, chunk_size); if (vsf_sysutil_retval_is_error(retval)) { ret_struct.retval = -2; return ret_struct; } else if (retval == 0 && !prev_cr) { /* Transfer done, nifty */ return ret_struct; } num_to_write = (unsigned int) retval; ret_struct.transferred += num_to_write; if (is_ascii) { /* Handle ASCII conversion if we have to. Note that using the same * buffer for source and destination is safe, because the ASCII -> * binary transform only ever results in a smaller file. */ struct ascii_to_bin_ret ret = vsf_ascii_ascii_to_bin(p_recvbuf, num_to_write, prev_cr); num_to_write = ret.stored; prev_cr = ret.last_was_cr; p_writebuf = ret.p_buf; } retval = vsf_sysutil_write_loop(file_fd, p_writebuf, num_to_write); if (vsf_sysutil_retval_is_error(retval) || (unsigned int) retval != num_to_write) { ret_struct.retval = -1; return ret_struct; } } } static unsigned int get_chunk_size() { unsigned int ret = VSFTP_DATA_BUFSIZE; if (tunable_trans_chunk_size < VSFTP_DATA_BUFSIZE && tunable_trans_chunk_size > 0) { ret = tunable_trans_chunk_size; if (ret < 4096) { ret = 4096; } } return ret; } vsftpd-3.0.3/oneprocess.h0000644000175000017500000000452211224705472014365 0ustar chrischris#ifndef VSF_ONEPROCESS_H #define VSF_ONEPROCESS_H struct mystr; struct vsf_session; /* vsf_one_process_start() * PURPOSE * Called to start FTP login processing using the one process model. Before * processing starts, all possible privileges are dropped. * PARAMETERS * p_sess - the current session object */ void vsf_one_process_start(struct vsf_session* p_sess); /* vsf_one_process_login() * PURPOSE * Called to propose a login using the one process model. Only anonymous * logins supported! * PARAMETERS * p_sess - the current session object * p_pass_str - the proposed password */ void vsf_one_process_login(struct vsf_session* p_sess, const struct mystr* p_pass_str); /* vsf_one_process_get_priv_data_sock() * PURPOSE * Get a privileged port 20 bound data socket using the one process model. * PARAMETERS * p_sess - the current session object * RETURNS * The file descriptor of the privileged socket */ int vsf_one_process_get_priv_data_sock(struct vsf_session* p_sess); /* vsf_one_process_pasv_cleanup() * PURPOSE * Clean up any listening passive socket. * PARAMETERS * p_sess - the current session object */ void vsf_one_process_pasv_cleanup(struct vsf_session* p_sess); /* vsf_one_process_pasv_active() * PURPOSE * Determine whether a listening pasv socket is active. * PARAMETERS * p_sess - the current session object * RETURNS * 1 if active, 0 if not. */ int vsf_one_process_pasv_active(struct vsf_session* p_sess); /* vsf_one_process_listen() * PURPOSE * Start listening for an incoming connection. * PARAMETERS * p_sess - the current session object * RETURNS * The port we listened on. */ unsigned short vsf_one_process_listen(struct vsf_session* p_sess); /* vsf_one_process_get_pasv_fd() * PURPOSE * Accept an incoming connection. * PARAMETERS * p_sess - the current session object * RETURNS * The file descriptor for the incoming connection. */ int vsf_one_process_get_pasv_fd(struct vsf_session* p_sess); /* vsf_one_process_chown_upload() * PURPOSE * Change ownership of an uploaded file using the one process model. * PARAMETERS * p_sess - the current session object * fd - the file descriptor to change ownership on */ void vsf_one_process_chown_upload(struct vsf_session* p_sess, int fd); #endif /* VSF_ONEPROCESS_H */ vsftpd-3.0.3/parseconf.c0000644000175000017500000003020612025276173014157 0ustar chrischris/* * Part of Very Secure FTPd * Licence: GPL v2 * Author: Chris Evans * parseconf.c * * Routines and support to load in a file full of tunable variables and * settings, populating corresponding runtime variables. */ #include "parseconf.h" #include "tunables.h" #include "str.h" #include "filestr.h" #include "defs.h" #include "sysutil.h" #include "utility.h" static const char* s_p_saved_filename; /* Tables mapping setting names to runtime variables */ /* Boolean settings */ static struct parseconf_bool_setting { const char* p_setting_name; int* p_variable; } parseconf_bool_array[] = { { "anonymous_enable", &tunable_anonymous_enable }, { "local_enable", &tunable_local_enable }, { "pasv_enable", &tunable_pasv_enable }, { "port_enable", &tunable_port_enable }, { "chroot_local_user", &tunable_chroot_local_user }, { "write_enable", &tunable_write_enable }, { "anon_upload_enable", &tunable_anon_upload_enable }, { "anon_mkdir_write_enable", &tunable_anon_mkdir_write_enable }, { "anon_other_write_enable", &tunable_anon_other_write_enable }, { "chown_uploads", &tunable_chown_uploads }, { "connect_from_port_20", &tunable_connect_from_port_20 }, { "xferlog_enable", &tunable_xferlog_enable }, { "dirmessage_enable", &tunable_dirmessage_enable }, { "anon_world_readable_only", &tunable_anon_world_readable_only }, { "async_abor_enable", &tunable_async_abor_enable }, { "ascii_upload_enable", &tunable_ascii_upload_enable }, { "ascii_download_enable", &tunable_ascii_download_enable }, { "one_process_model", &tunable_one_process_model }, { "xferlog_std_format", &tunable_xferlog_std_format }, { "pasv_promiscuous", &tunable_pasv_promiscuous }, { "deny_email_enable", &tunable_deny_email_enable }, { "chroot_list_enable", &tunable_chroot_list_enable }, { "setproctitle_enable", &tunable_setproctitle_enable }, { "text_userdb_names", &tunable_text_userdb_names }, { "ls_recurse_enable", &tunable_ls_recurse_enable }, { "log_ftp_protocol", &tunable_log_ftp_protocol }, { "guest_enable", &tunable_guest_enable }, { "userlist_enable", &tunable_userlist_enable }, { "userlist_deny", &tunable_userlist_deny }, { "use_localtime", &tunable_use_localtime }, { "check_shell", &tunable_check_shell }, { "hide_ids", &tunable_hide_ids }, { "listen", &tunable_listen }, { "port_promiscuous", &tunable_port_promiscuous }, { "passwd_chroot_enable", &tunable_passwd_chroot_enable }, { "no_anon_password", &tunable_no_anon_password }, { "tcp_wrappers", &tunable_tcp_wrappers }, { "use_sendfile", &tunable_use_sendfile }, { "force_dot_files", &tunable_force_dot_files }, { "listen_ipv6", &tunable_listen_ipv6 }, { "dual_log_enable", &tunable_dual_log_enable }, { "syslog_enable", &tunable_syslog_enable }, { "background", &tunable_background }, { "virtual_use_local_privs", &tunable_virtual_use_local_privs }, { "session_support", &tunable_session_support }, { "download_enable", &tunable_download_enable }, { "dirlist_enable", &tunable_dirlist_enable }, { "chmod_enable", &tunable_chmod_enable }, { "secure_email_list_enable", &tunable_secure_email_list_enable }, { "run_as_launching_user", &tunable_run_as_launching_user }, { "no_log_lock", &tunable_no_log_lock }, { "ssl_enable", &tunable_ssl_enable }, { "allow_anon_ssl", &tunable_allow_anon_ssl }, { "force_local_logins_ssl", &tunable_force_local_logins_ssl }, { "force_local_data_ssl", &tunable_force_local_data_ssl }, { "ssl_sslv2", &tunable_sslv2 }, { "ssl_sslv3", &tunable_sslv3 }, { "ssl_tlsv1", &tunable_tlsv1 }, { "tilde_user_enable", &tunable_tilde_user_enable }, { "force_anon_logins_ssl", &tunable_force_anon_logins_ssl }, { "force_anon_data_ssl", &tunable_force_anon_data_ssl }, { "mdtm_write", &tunable_mdtm_write }, { "lock_upload_files", &tunable_lock_upload_files }, { "pasv_addr_resolve", &tunable_pasv_addr_resolve }, { "debug_ssl", &tunable_debug_ssl }, { "require_cert", &tunable_require_cert }, { "validate_cert", &tunable_validate_cert }, { "strict_ssl_read_eof", &tunable_strict_ssl_read_eof }, { "strict_ssl_write_shutdown", &tunable_strict_ssl_write_shutdown }, { "ssl_request_cert", &tunable_ssl_request_cert }, { "delete_failed_uploads", &tunable_delete_failed_uploads }, { "implicit_ssl", &tunable_implicit_ssl }, { "ptrace_sandbox", &tunable_ptrace_sandbox }, { "require_ssl_reuse", &tunable_require_ssl_reuse }, { "isolate", &tunable_isolate }, { "isolate_network", &tunable_isolate_network }, { "ftp_enable", &tunable_ftp_enable }, { "http_enable", &tunable_http_enable }, { "seccomp_sandbox", &tunable_seccomp_sandbox }, { "allow_writeable_chroot", &tunable_allow_writeable_chroot }, { 0, 0 } }; static struct parseconf_uint_setting { const char* p_setting_name; unsigned int* p_variable; } parseconf_uint_array[] = { { "accept_timeout", &tunable_accept_timeout }, { "connect_timeout", &tunable_connect_timeout }, { "local_umask", &tunable_local_umask }, { "anon_umask", &tunable_anon_umask }, { "ftp_data_port", &tunable_ftp_data_port }, { "idle_session_timeout", &tunable_idle_session_timeout }, { "data_connection_timeout", &tunable_data_connection_timeout }, { "pasv_min_port", &tunable_pasv_min_port }, { "pasv_max_port", &tunable_pasv_max_port }, { "anon_max_rate", &tunable_anon_max_rate }, { "local_max_rate", &tunable_local_max_rate }, { "listen_port", &tunable_listen_port }, { "max_clients", &tunable_max_clients }, { "file_open_mode", &tunable_file_open_mode }, { "max_per_ip", &tunable_max_per_ip }, { "trans_chunk_size", &tunable_trans_chunk_size }, { "delay_failed_login", &tunable_delay_failed_login }, { "delay_successful_login", &tunable_delay_successful_login }, { "max_login_fails", &tunable_max_login_fails }, { "chown_upload_mode", &tunable_chown_upload_mode }, { 0, 0 } }; static struct parseconf_str_setting { const char* p_setting_name; const char** p_variable; } parseconf_str_array[] = { { "secure_chroot_dir", &tunable_secure_chroot_dir }, { "ftp_username", &tunable_ftp_username }, { "chown_username", &tunable_chown_username }, { "xferlog_file", &tunable_xferlog_file }, { "vsftpd_log_file", &tunable_vsftpd_log_file }, { "message_file", &tunable_message_file }, { "nopriv_user", &tunable_nopriv_user }, { "ftpd_banner", &tunable_ftpd_banner }, { "banned_email_file", &tunable_banned_email_file }, { "chroot_list_file", &tunable_chroot_list_file }, { "pam_service_name", &tunable_pam_service_name }, { "guest_username", &tunable_guest_username }, { "userlist_file", &tunable_userlist_file }, { "anon_root", &tunable_anon_root }, { "local_root", &tunable_local_root }, { "banner_file", &tunable_banner_file }, { "pasv_address", &tunable_pasv_address }, { "listen_address", &tunable_listen_address }, { "user_config_dir", &tunable_user_config_dir }, { "listen_address6", &tunable_listen_address6 }, { "cmds_allowed", &tunable_cmds_allowed }, { "hide_file", &tunable_hide_file }, { "deny_file", &tunable_deny_file }, { "user_sub_token", &tunable_user_sub_token }, { "email_password_file", &tunable_email_password_file }, { "rsa_cert_file", &tunable_rsa_cert_file }, { "dsa_cert_file", &tunable_dsa_cert_file }, { "ssl_ciphers", &tunable_ssl_ciphers }, { "rsa_private_key_file", &tunable_rsa_private_key_file }, { "dsa_private_key_file", &tunable_dsa_private_key_file }, { "ca_certs_file", &tunable_ca_certs_file }, { "cmds_denied", &tunable_cmds_denied }, { 0, 0 } }; void vsf_parseconf_load_file(const char* p_filename, int errs_fatal) { struct mystr config_file_str = INIT_MYSTR; struct mystr config_setting_str = INIT_MYSTR; struct mystr config_value_str = INIT_MYSTR; unsigned int str_pos = 0; int retval; if (!p_filename) { p_filename = s_p_saved_filename; } else { if (s_p_saved_filename) { vsf_sysutil_free((char*)s_p_saved_filename); } s_p_saved_filename = vsf_sysutil_strdup(p_filename); } if (!p_filename) { bug("null filename in vsf_parseconf_load_file"); } retval = str_fileread(&config_file_str, p_filename, VSFTP_CONF_FILE_MAX); if (vsf_sysutil_retval_is_error(retval)) { if (errs_fatal) { die2("cannot read config file: ", p_filename); } else { str_free(&config_file_str); return; } } { struct vsf_sysutil_statbuf* p_statbuf = 0; retval = vsf_sysutil_stat(p_filename, &p_statbuf); /* Security: check current user owns the config file. These are sanity * checks for the admin, and are NOT designed to be checks safe from * race conditions. */ if (vsf_sysutil_retval_is_error(retval) || vsf_sysutil_statbuf_get_uid(p_statbuf) != vsf_sysutil_getuid() || !vsf_sysutil_statbuf_is_regfile(p_statbuf)) { die("config file not owned by correct user, or not a file"); } vsf_sysutil_free(p_statbuf); } while (str_getline(&config_file_str, &config_setting_str, &str_pos)) { if (str_isempty(&config_setting_str) || str_get_char_at(&config_setting_str, 0) == '#' || str_all_space(&config_setting_str)) { continue; } vsf_parseconf_load_setting(str_getbuf(&config_setting_str), errs_fatal); } str_free(&config_file_str); str_free(&config_setting_str); str_free(&config_value_str); } void vsf_parseconf_load_setting(const char* p_setting, int errs_fatal) { static struct mystr s_setting_str; static struct mystr s_value_str; while (vsf_sysutil_isspace(*p_setting)) { p_setting++; } str_alloc_text(&s_setting_str, p_setting); str_split_char(&s_setting_str, &s_value_str, '='); /* Is it a string setting? */ { const struct parseconf_str_setting* p_str_setting = parseconf_str_array; while (p_str_setting->p_setting_name != 0) { if (str_equal_text(&s_setting_str, p_str_setting->p_setting_name)) { /* Got it */ const char** p_curr_setting = p_str_setting->p_variable; if (*p_curr_setting) { vsf_sysutil_free((char*) *p_curr_setting); } if (str_isempty(&s_value_str)) { *p_curr_setting = 0; } else { *p_curr_setting = str_strdup(&s_value_str); } return; } p_str_setting++; } } if (str_isempty(&s_value_str)) { if (errs_fatal) { die2("missing value in config file for: ", str_getbuf(&s_setting_str)); } else { return; } } /* Is it a boolean value? */ { const struct parseconf_bool_setting* p_bool_setting = parseconf_bool_array; while (p_bool_setting->p_setting_name != 0) { if (str_equal_text(&s_setting_str, p_bool_setting->p_setting_name)) { /* Got it */ str_upper(&s_value_str); if (str_equal_text(&s_value_str, "YES") || str_equal_text(&s_value_str, "TRUE") || str_equal_text(&s_value_str, "1")) { *(p_bool_setting->p_variable) = 1; } else if (str_equal_text(&s_value_str, "NO") || str_equal_text(&s_value_str, "FALSE") || str_equal_text(&s_value_str, "0")) { *(p_bool_setting->p_variable) = 0; } else if (errs_fatal) { die2("bad bool value in config file for: ", str_getbuf(&s_setting_str)); } return; } p_bool_setting++; } } /* Is it an unsigned integer setting? */ { const struct parseconf_uint_setting* p_uint_setting = parseconf_uint_array; while (p_uint_setting->p_setting_name != 0) { if (str_equal_text(&s_setting_str, p_uint_setting->p_setting_name)) { /* Got it */ /* If the value starts with 0, assume it's an octal value */ if (!str_isempty(&s_value_str) && str_get_char_at(&s_value_str, 0) == '0') { *(p_uint_setting->p_variable) = str_octal_to_uint(&s_value_str); } else { /* TODO: we could reject negatives instead of converting them? */ *(p_uint_setting->p_variable) = (unsigned int) str_atoi(&s_value_str); } return; } p_uint_setting++; } } if (errs_fatal) { die2("unrecognised variable in config file: ", str_getbuf(&s_setting_str)); } } vsftpd-3.0.3/utility.h0000644000175000017500000000215210750743701013705 0ustar chrischris#ifndef VSF_UTILITY_H #define VSF_UTILITY_H struct mystr; /* die() * PURPOSE * Terminate execution of the process, due to an abnormal (but non-bug) * situation. * PARAMETERS * p_text - text string describing why the process is exiting */ void die(const char* p_text); /* die2() * PURPOSE * Terminate execution of the process, due to an abnormal (but non-bug) * situation. * PARAMETERS * p_text1 - text string describing why the process is exiting * p_text2 - text to safely concatenate to p_text1 */ void die2(const char* p_text1, const char* p_text2); /* bug() * PURPOSE * Terminate execution of the process, due to a suspected bug, trying to emit * the reason this happened down the network in FTP response format. * PARAMETERS * p_text - text string describing what bug trap has triggered * */ void bug(const char* p_text); /* vsf_exit() * PURPOSE * Terminate execution of the process, writing out the specified text string * in the process. * PARAMETERS * p_text - text string describing why the process is exiting */ void vsf_exit(const char* p_text); #endif vsftpd-3.0.3/vsftpver.h0000644000175000017500000000014612553270410014055 0ustar chrischris#ifndef VSF_VERSION_H #define VSF_VERSION_H #define VSF_VERSION "3.0.3" #endif /* VSF_VERSION_H */ vsftpd-3.0.3/ftppolicy.c0000644000175000017500000002225111702242420014176 0ustar chrischris/* * Part of Very Secure FTPd * Licence: GPL v2 * Author: Chris Evans * ftppolicy.c * * Code to build a sandbox policy based on current session options. */ #include "ftppolicy.h" #include "ptracesandbox.h" #include "tunables.h" #include "session.h" #include "sysutil.h" /* For AF_INET etc. network constants. */ #include #include #include #include #include #include static int socket_validator(struct pt_sandbox* p_sandbox, void* p_arg); static int connect_validator(struct pt_sandbox* p_sandbox, void* p_arg); static int getsockopt_validator(struct pt_sandbox* p_sandbox, void* p_arg); static int setsockopt_validator(struct pt_sandbox* p_sandbox, void* p_arg); void policy_setup(struct pt_sandbox* p_sandbox, const struct vsf_session* p_sess) { int is_anon = p_sess->is_anonymous; /* Always need to be able to exit! */ ptrace_sandbox_permit_exit(p_sandbox); /* Needed for memory management. */ ptrace_sandbox_permit_mmap(p_sandbox); ptrace_sandbox_permit_mprotect(p_sandbox); ptrace_sandbox_permit_brk(p_sandbox); /* Simple reads and writes are required. Permitting write does not imply * filesystem write access because access control is done at open time. */ ptrace_sandbox_permit_read(p_sandbox); ptrace_sandbox_permit_write(p_sandbox); /* Reading FTP commands from the network. */ ptrace_sandbox_permit_recv(p_sandbox); /* Querying time is harmless; used for log timestamps and internally to * OpenSSL */ ptrace_sandbox_permit_query_time(p_sandbox); /* Typically post-login things follow. */ /* Since we're in a chroot(), we can just blanket allow filesystem readonly * open. */ ptrace_sandbox_permit_open(p_sandbox, 0); ptrace_sandbox_permit_close(p_sandbox); /* Other pathname-based metadata queries. */ ptrace_sandbox_permit_file_stats(p_sandbox); ptrace_sandbox_permit_readlink(p_sandbox); /* Querying, reading and changing directory. */ ptrace_sandbox_permit_getcwd(p_sandbox); ptrace_sandbox_permit_chdir(p_sandbox); ptrace_sandbox_permit_getdents(p_sandbox); /* Simple fd-based operations. */ ptrace_sandbox_permit_fd_stats(p_sandbox); ptrace_sandbox_permit_seek(p_sandbox); ptrace_sandbox_permit_shutdown(p_sandbox); ptrace_sandbox_permit_fcntl(p_sandbox); ptrace_sandbox_permit_setsockopt(p_sandbox); ptrace_sandbox_set_setsockopt_validator(p_sandbox, setsockopt_validator, 0); /* Misc */ /* Setting umask. */ ptrace_sandbox_permit_umask(p_sandbox); /* Select for data connection readyness. */ ptrace_sandbox_permit_select(p_sandbox); /* Always need ability to take signals (SIGPIPE) */ ptrace_sandbox_permit_sigreturn(p_sandbox); /* Sleeping (bandwidth limit, connect retires, anon login fails) */ ptrace_sandbox_permit_sleep(p_sandbox); /* High-speed transfers... */ ptrace_sandbox_permit_sendfile(p_sandbox); /* TODO - Grrrr! nscd cache access is leaking into child. Need to find out * out how to disable that. Also means that text_userdb_names loads values * from the real system data. */ if (tunable_text_userdb_names) { ptrace_sandbox_permit_mremap(p_sandbox); } /* May need ability to install signal handlers. */ if (tunable_async_abor_enable || tunable_idle_session_timeout > 0 || tunable_data_connection_timeout > 0) { ptrace_sandbox_permit_sigaction(p_sandbox); } /* May need ability to set up timeout alarms. */ if (tunable_idle_session_timeout > 0 || tunable_data_connection_timeout > 0) { ptrace_sandbox_permit_alarm(p_sandbox); } /* Set up network permissions according to config and session. */ ptrace_sandbox_permit_socket(p_sandbox); ptrace_sandbox_set_socket_validator(p_sandbox, socket_validator, (void*) p_sess); ptrace_sandbox_permit_bind(p_sandbox); /* Yes, reuse of the connect validator is intentional. */ ptrace_sandbox_set_bind_validator(p_sandbox, connect_validator, (void*) p_sess); if (tunable_port_enable) { ptrace_sandbox_permit_connect(p_sandbox); ptrace_sandbox_set_connect_validator(p_sandbox, connect_validator, (void*) p_sess); ptrace_sandbox_permit_getsockopt(p_sandbox); ptrace_sandbox_set_getsockopt_validator(p_sandbox, getsockopt_validator, 0); } if (tunable_pasv_enable) { ptrace_sandbox_permit_listen(p_sandbox); ptrace_sandbox_permit_accept(p_sandbox); } /* Set up write permissions according to config and session. */ if (tunable_write_enable) { if (!is_anon || tunable_anon_upload_enable) { ptrace_sandbox_permit_open(p_sandbox, 1); } if (!is_anon || tunable_anon_mkdir_write_enable) { ptrace_sandbox_permit_mkdir(p_sandbox); } if (!is_anon || tunable_anon_other_write_enable) { ptrace_sandbox_permit_unlink(p_sandbox); ptrace_sandbox_permit_rmdir(p_sandbox); ptrace_sandbox_permit_rename(p_sandbox); ptrace_sandbox_permit_ftruncate(p_sandbox); if (tunable_mdtm_write) { ptrace_sandbox_permit_utime(p_sandbox); } } if (!is_anon && tunable_chmod_enable) { ptrace_sandbox_permit_chmod(p_sandbox); } if (is_anon && tunable_chown_uploads) { ptrace_sandbox_permit_fchmod(p_sandbox); ptrace_sandbox_permit_fchown(p_sandbox); } } } static int socket_validator(struct pt_sandbox* p_sandbox, void* p_arg) { int ret; struct vsf_session* p_sess = (struct vsf_session*) p_arg; unsigned long arg1; unsigned long arg2; unsigned long expected_family = AF_INET; if (vsf_sysutil_sockaddr_is_ipv6(p_sess->p_local_addr)) { expected_family = AF_INET6; } ret = ptrace_sandbox_get_socketcall_arg(p_sandbox, 0, &arg1); if (ret != 0) { return ret; } ret = ptrace_sandbox_get_socketcall_arg(p_sandbox, 1, &arg2); if (ret != 0) { return ret; } if (arg1 != expected_family || arg2 != SOCK_STREAM) { return -1; } return 0; } static int connect_validator(struct pt_sandbox* p_sandbox, void* p_arg) { int ret; struct vsf_session* p_sess = (struct vsf_session*) p_arg; unsigned long arg2; unsigned long arg3; unsigned long expected_family = AF_INET; unsigned long expected_len = sizeof(struct sockaddr_in); void* p_buf = 0; struct sockaddr* p_sockaddr; static struct vsf_sysutil_sockaddr* p_sockptr; if (vsf_sysutil_sockaddr_is_ipv6(p_sess->p_local_addr)) { expected_family = AF_INET6; expected_len = sizeof(struct sockaddr_in6); } ret = ptrace_sandbox_get_socketcall_arg(p_sandbox, 1, &arg2); if (ret != 0) { return ret; } ret = ptrace_sandbox_get_socketcall_arg(p_sandbox, 2, &arg3); if (ret != 0) { return ret; } if (arg3 != expected_len) { return -1; } p_buf = vsf_sysutil_malloc((int) expected_len); ret = ptrace_sandbox_get_buf(p_sandbox, arg2, expected_len, p_buf); if (ret != 0) { vsf_sysutil_free(p_buf); return -2; } p_sockaddr = (struct sockaddr*) p_buf; if (p_sockaddr->sa_family != expected_family) { vsf_sysutil_free(p_buf); return -3; } if (expected_family == AF_INET) { struct sockaddr_in* p_sockaddr_in = (struct sockaddr_in*) p_sockaddr; vsf_sysutil_sockaddr_alloc_ipv4(&p_sockptr); vsf_sysutil_sockaddr_set_ipv4addr(p_sockptr, (const unsigned char*) &p_sockaddr_in->sin_addr); } else { struct sockaddr_in6* p_sockaddr_in6 = (struct sockaddr_in6*) p_sockaddr; vsf_sysutil_sockaddr_alloc_ipv6(&p_sockptr); vsf_sysutil_sockaddr_set_ipv6addr(p_sockptr, (const unsigned char*) &p_sockaddr_in6->sin6_addr); } if (!vsf_sysutil_sockaddr_addr_equal(p_sess->p_remote_addr, p_sockptr)) { vsf_sysutil_free(p_buf); return -4; } vsf_sysutil_free(p_buf); return 0; } static int getsockopt_validator(struct pt_sandbox* p_sandbox, void* p_arg) { int ret; unsigned long arg2; unsigned long arg3; (void) p_arg; ret = ptrace_sandbox_get_socketcall_arg(p_sandbox, 1, &arg2); if (ret != 0) { return ret; } ret = ptrace_sandbox_get_socketcall_arg(p_sandbox, 2, &arg3); if (ret != 0) { return ret; } if (arg2 != SOL_SOCKET || arg3 != SO_ERROR) { return -1; } return 0; } static int setsockopt_validator(struct pt_sandbox* p_sandbox, void* p_arg) { int ret; unsigned long arg2; unsigned long arg3; (void) p_arg; ret = ptrace_sandbox_get_socketcall_arg(p_sandbox, 1, &arg2); if (ret != 0) { return ret; } ret = ptrace_sandbox_get_socketcall_arg(p_sandbox, 2, &arg3); if (ret != 0) { return ret; } if (arg2 == SOL_SOCKET) { if (arg3 != SO_KEEPALIVE && arg3 != SO_REUSEADDR && arg3 != SO_OOBINLINE && arg3 != SO_LINGER) { return -1; } } else if (arg2 == IPPROTO_TCP) { if (arg3 != TCP_NODELAY) { return -2; } } else if (arg2 == IPPROTO_IP) { if (arg3 != IP_TOS) { return -3; } } else { return -4; } return 0; } vsftpd-3.0.3/privsock.h0000644000175000017500000001141411224704416014040 0ustar chrischris#ifndef VSF_PRIVSOCK_H #define VSF_PRIVSOCK_H struct mystr; struct vsf_session; /* priv_sock_init() * PURPOSE * Initialize the priv_sock system, by opening the communications sockets. * * PARAMETERS * p_sess - the current session object */ void priv_sock_init(struct vsf_session* p_sess); /* priv_sock_close() * PURPOSE * Closes any open file descriptors relating to the priv_sock system. * * PARAMETERS * p_sess - the current session object */ void priv_sock_close(struct vsf_session* p_sess); /* priv_sock_set_parent_context() * PURPOSE * Closes the child's fd, e.g. p_sess->child_fd. * * PARAMETERS * p_sess - the current session object */ void priv_sock_set_parent_context(struct vsf_session* p_sess); /* priv_sock_set_child_context() * PURPOSE * Closes the parent's fd, e.g. p_sess->parent_fd. * * PARAMETERS * p_sess - the current session object */ void priv_sock_set_child_context(struct vsf_session* p_sess); /* priv_sock_send_cmd() * PURPOSE * Sends a command, typically to the privileged side of the channel. * PARAMETERS * fd - the fd on which to send the command * cmd - the command to send */ void priv_sock_send_cmd(int fd, char cmd); /* priv_sock_send_str() * PURPOSE * Sends a string to the other side of the channel. * PARAMETERS * fd - the fd on which to send the string * p_str - the string to send */ void priv_sock_send_str(int fd, const struct mystr* p_str); /* priv_sock_send_buf() * PURPOSE * Sends a buffer to the other side of the channel. The protocol used is the * same as priv_sock_send_str() * PARAMETERS * fd - the fd on which to send the buffer * p_buf - the buffer to send * len - length of the buffer */ void priv_sock_send_buf(int fd, const char* p_buf, unsigned int len); /* priv_sock_recv_buf() * PURPOSE * Receives a buffer from the other side of the channel. The protocol used is * the same as priv_sock_recv_str() * PARAMETERS * fd - the fd on which to receive the buffer * p_buf - the buffer to write into * len - length of the buffer */ void priv_sock_recv_buf(int fd, char* p_buf, unsigned int len); /* priv_sock_get_result() * PURPOSE * Receives a response, typically from the privileged side of the channel. * PARAMETERS * fd - the fd on which to receive the response * RETURNS * The response code. */ char priv_sock_get_result(int fd); /* priv_sock_get_cmd() * PURPOSE * Receives a command, typically on the privileged side of the channel. * PARAMETERS * fd - the fd on which to receive the command. * RETURNS * The command that was sent. */ char priv_sock_get_cmd(int fd); /* priv_sock_get_str() * PURPOSE * Receives a string from the other side of the channel. * PARAMETERS * fd - the fd on which to receive the string * p_dest - where to copy the received string */ void priv_sock_get_str(int fd, struct mystr* p_dest); /* priv_sock_send_result() * PURPOSE * Sends a command result, typically to the unprivileged side of the channel. * PARAMETERS * fd - the fd on which to send the result * res - the result to send */ void priv_sock_send_result(int fd, char res); /* priv_sock_send_fd() * PURPOSE * Sends a file descriptor to the other side of the channel. * PARAMETERS * fd - the fd on which to send the descriptor * send_fd - the descriptor to send */ void priv_sock_send_fd(int fd, int send_fd); /* priv_sock_recv_fd() * PURPOSE * Receives a file descriptor from the other side of the channel. * PARAMETERS * fd - the fd on which to receive the descriptor * RETURNS * The received file descriptor */ int priv_sock_recv_fd(int fd); /* priv_sock_send_int() * PURPOSE * Sends an integer to the other side of the channel. * PARAMETERS * fd - the fd on which to send the integer * the_int - the integer to send */ void priv_sock_send_int(int fd, int the_int); /* priv_sock_get_int() * PURPOSE * Receives an integer from the other side of the channel. * PARAMETERS * fd - the fd on which to receive the integer * RETURNS * The integer that was sent. */ int priv_sock_get_int(int fd); #define PRIV_SOCK_LOGIN 1 #define PRIV_SOCK_CHOWN 2 #define PRIV_SOCK_GET_DATA_SOCK 3 #define PRIV_SOCK_GET_USER_CMD 4 #define PRIV_SOCK_WRITE_USER_RESP 5 #define PRIV_SOCK_DO_SSL_HANDSHAKE 6 #define PRIV_SOCK_DO_SSL_CLOSE 7 #define PRIV_SOCK_DO_SSL_READ 8 #define PRIV_SOCK_DO_SSL_WRITE 9 #define PRIV_SOCK_PASV_CLEANUP 10 #define PRIV_SOCK_PASV_ACTIVE 11 #define PRIV_SOCK_PASV_LISTEN 12 #define PRIV_SOCK_PASV_ACCEPT 13 #define PRIV_SOCK_RESULT_OK 1 #define PRIV_SOCK_RESULT_BAD 2 #endif /* VSF_PRIVSOCK_H */ vsftpd-3.0.3/tunables.h0000644000175000017500000002065711734500457014033 0ustar chrischris#ifndef VSF_TUNABLES_H #define VSF_TUNABLES_H /* tunables_load_defaults() * PURPOSE * Load the default values into the global settings variables. */ void tunables_load_defaults(); /* Configurable preferences */ /* Booleans */ extern int tunable_anonymous_enable; /* Allow anon logins */ extern int tunable_local_enable; /* Allow local logins */ extern int tunable_pasv_enable; /* Allow PASV */ extern int tunable_port_enable; /* Allow PORT */ extern int tunable_chroot_local_user; /* Restrict local to home dir */ extern int tunable_write_enable; /* Global enable writes */ extern int tunable_anon_upload_enable; /* Enable STOR for anon users */ extern int tunable_anon_mkdir_write_enable; /* MKD for anon */ extern int tunable_anon_other_write_enable; /* APPE DELE RMD RNFR for anon */ extern int tunable_chown_uploads; /* chown() anon uploaded files */ extern int tunable_connect_from_port_20; /* PORT connects from port 20 */ extern int tunable_xferlog_enable; /* Log transfers to a file */ extern int tunable_dirmessage_enable; /* Look for + output .message */ extern int tunable_anon_world_readable_only; /* Only serve world readable */ extern int tunable_async_abor_enable; /* Enable async ABOR requests */ extern int tunable_ascii_upload_enable; /* Permit ASCII upload */ extern int tunable_ascii_download_enable; /* Permit ASCII download */ extern int tunable_one_process_model; /* Go faster stripes ;-) */ extern int tunable_xferlog_std_format; /* Log details like wu-ftpd */ extern int tunable_pasv_promiscuous; /* Allow any PASV connect IP */ extern int tunable_deny_email_enable; /* Ban a list of anon e-mails */ extern int tunable_chroot_list_enable; /* chroot() based on list file */ extern int tunable_setproctitle_enable; /* Try to use setproctitle() */ extern int tunable_text_userdb_names; /* For "ls", lookup text names */ extern int tunable_ls_recurse_enable; /* Allow ls -R */ extern int tunable_log_ftp_protocol; /* Log FTP requests/responses */ extern int tunable_guest_enable; /* Remap guest users */ extern int tunable_userlist_enable; /* Explicit user allow or deny */ extern int tunable_userlist_deny; /* Is user list allow or deny? */ extern int tunable_use_localtime; /* Use local time or GMT? */ extern int tunable_check_shell; /* Use /etc/shells for non-PAM */ extern int tunable_hide_ids; /* Show "ftp" in ls listings */ extern int tunable_listen; /* Standalone (no inetd) mode? */ extern int tunable_port_promiscuous; /* Any any PORT connect IP */ extern int tunable_passwd_chroot_enable; /* chroot() based on passwd */ extern int tunable_no_anon_password; /* Do not ask for anon pword */ extern int tunable_tcp_wrappers; /* Standalone: do tcp wrappers */ extern int tunable_use_sendfile; /* Use sendfile() if we can */ extern int tunable_force_dot_files; /* Show dotfiles without -a */ extern int tunable_listen_ipv6; /* Standalone with IPv6 listen */ extern int tunable_dual_log_enable; /* Log vsftpd.log AND xferlog */ extern int tunable_syslog_enable; /* Use syslog not vsftpd.log */ extern int tunable_background; /* Background listener process */ extern int tunable_virtual_use_local_privs; /* Virtual user => local privs */ extern int tunable_session_support; /* utmp, wtmp, pam_session */ extern int tunable_download_enable; /* Can download anything? */ extern int tunable_dirlist_enable; /* Can see any dirs? */ extern int tunable_chmod_enable; /* Is CHMOD allowed? (local) */ extern int tunable_secure_email_list_enable; /* Require specific anon email */ extern int tunable_run_as_launching_user; /* Runs as launching user */ extern int tunable_no_log_lock; /* Don't lock log files */ extern int tunable_ssl_enable; /* Allow SSL/TLS AUTH */ extern int tunable_allow_anon_ssl; /* Allow anonymous use of SSL */ extern int tunable_force_local_logins_ssl; /* Require local logins use SSL */ extern int tunable_force_local_data_ssl; /* Require local data uses SSL */ extern int tunable_sslv2; /* Allow SSLv2 */ extern int tunable_sslv3; /* Allow SSLv3 */ extern int tunable_tlsv1; /* Allow TLSv1 */ extern int tunable_tilde_user_enable; /* Support e.g. ~chris */ extern int tunable_force_anon_logins_ssl; /* Require anon logins use SSL */ extern int tunable_force_anon_data_ssl; /* Require anon data uses SSL */ extern int tunable_mdtm_write; /* Allow MDTM to set timestamps */ extern int tunable_lock_upload_files; /* Lock uploading files */ extern int tunable_pasv_addr_resolve; /* DNS resolve pasv_addr */ extern int tunable_debug_ssl; /* Verbose SSL logging */ extern int tunable_require_cert; /* SSL client cert required */ extern int tunable_validate_cert; /* SSL certs must be valid */ extern int tunable_strict_ssl_read_eof; /* Need SSL_shutdown() on read */ extern int tunable_strict_ssl_write_shutdown; /* Need SSL_shutdown() on write */ extern int tunable_ssl_request_cert; /* Ask client for cert */ extern int tunable_delete_failed_uploads; /* Delete an upload that failed */ extern int tunable_implicit_ssl; /* Use implicit SSL protocol */ extern int tunable_ptrace_sandbox; /* DEPRECATED ptrace sandbox */ extern int tunable_require_ssl_reuse; /* Require re-used data conn */ extern int tunable_isolate; /* Use container clone() flags */ extern int tunable_isolate_network; /* Use CLONE_NEWNET */ extern int tunable_ftp_enable; /* Allow FTP protocol */ extern int tunable_http_enable; /* Allow HTTP protocol */ extern int tunable_seccomp_sandbox; /* seccomp filter sandbox */ extern int tunable_allow_writeable_chroot; /* Allow misconfiguration */ /* Integer/numeric defines */ extern unsigned int tunable_accept_timeout; extern unsigned int tunable_connect_timeout; extern unsigned int tunable_local_umask; extern unsigned int tunable_anon_umask; extern unsigned int tunable_ftp_data_port; extern unsigned int tunable_idle_session_timeout; extern unsigned int tunable_data_connection_timeout; extern unsigned int tunable_pasv_min_port; extern unsigned int tunable_pasv_max_port; extern unsigned int tunable_anon_max_rate; extern unsigned int tunable_local_max_rate; extern unsigned int tunable_listen_port; extern unsigned int tunable_max_clients; extern unsigned int tunable_file_open_mode; extern unsigned int tunable_max_per_ip; extern unsigned int tunable_trans_chunk_size; extern unsigned int tunable_delay_failed_login; extern unsigned int tunable_delay_successful_login; extern unsigned int tunable_max_login_fails; extern unsigned int tunable_chown_upload_mode; /* String defines */ extern const char* tunable_secure_chroot_dir; extern const char* tunable_ftp_username; extern const char* tunable_chown_username; extern const char* tunable_xferlog_file; extern const char* tunable_vsftpd_log_file; extern const char* tunable_message_file; extern const char* tunable_nopriv_user; extern const char* tunable_ftpd_banner; extern const char* tunable_banned_email_file; extern const char* tunable_chroot_list_file; extern const char* tunable_pam_service_name; extern const char* tunable_guest_username; extern const char* tunable_userlist_file; extern const char* tunable_anon_root; extern const char* tunable_local_root; extern const char* tunable_banner_file; extern const char* tunable_pasv_address; extern const char* tunable_listen_address; extern const char* tunable_user_config_dir; extern const char* tunable_listen_address6; extern const char* tunable_cmds_allowed; extern const char* tunable_hide_file; extern const char* tunable_deny_file; extern const char* tunable_user_sub_token; extern const char* tunable_email_password_file; extern const char* tunable_rsa_cert_file; extern const char* tunable_dsa_cert_file; extern const char* tunable_ssl_ciphers; extern const char* tunable_rsa_private_key_file; extern const char* tunable_dsa_private_key_file; extern const char* tunable_ca_certs_file; extern const char* tunable_cmds_denied; #endif /* VSF_TUNABLES_H */ vsftpd-3.0.3/privops.c0000644000175000017500000002626412025276764013720 0ustar chrischris/* * Part of Very Secure FTPd * License: GPL v2 * Author: Chris Evans * privops.c * * Code implementing the privileged operations that the unprivileged client * might request. * Look for suitable paranoia in this file. */ #include "privops.h" #include "session.h" #include "sysdeputil.h" #include "sysutil.h" #include "utility.h" #include "str.h" #include "tunables.h" #include "defs.h" #include "logging.h" /* File private functions */ static enum EVSFPrivopLoginResult handle_anonymous_login( struct vsf_session* p_sess, const struct mystr* p_pass_str); static enum EVSFPrivopLoginResult handle_local_login( struct vsf_session* p_sess, struct mystr* p_user_str, const struct mystr* p_pass_str); static void setup_username_globals(struct vsf_session* p_sess, const struct mystr* p_str); static enum EVSFPrivopLoginResult handle_login( struct vsf_session* p_sess, struct mystr* p_user_str, const struct mystr* p_pass_str); int vsf_privop_get_ftp_port_sock(struct vsf_session* p_sess, unsigned short remote_port, int use_port_sockaddr) { static struct vsf_sysutil_sockaddr* p_sockaddr; const struct vsf_sysutil_sockaddr* p_connect_to; int retval; int i; int s = vsf_sysutil_get_ipsock(p_sess->p_local_addr); unsigned short port = 0; if (p_sess->pasv_listen_fd != -1) { die("listed fd is active?"); } if (vsf_sysutil_is_port_reserved(remote_port)) { die("Illegal port request"); } if (tunable_connect_from_port_20) { port = (unsigned short) tunable_ftp_data_port; } vsf_sysutil_activate_reuseaddr(s); /* A report of failure here on Solaris, presumably buggy address reuse * support? We'll retry. */ for (i = 0; i < 2; ++i) { double sleep_for; vsf_sysutil_sockaddr_clone(&p_sockaddr, p_sess->p_local_addr); vsf_sysutil_sockaddr_set_port(p_sockaddr, port); retval = vsf_sysutil_bind(s, p_sockaddr); if (retval == 0) { break; } if (vsf_sysutil_get_error() != kVSFSysUtilErrADDRINUSE || i == 1) { die("vsf_sysutil_bind"); } sleep_for = vsf_sysutil_get_random_byte(); sleep_for /= 256.0; sleep_for += 1.0; vsf_sysutil_sleep(sleep_for); } if (use_port_sockaddr) { p_connect_to = p_sess->p_port_sockaddr; } else { vsf_sysutil_sockaddr_set_port(p_sess->p_remote_addr, remote_port); p_connect_to = p_sess->p_remote_addr; } retval = vsf_sysutil_connect_timeout(s, p_connect_to, tunable_connect_timeout); if (vsf_sysutil_retval_is_error(retval)) { vsf_sysutil_close(s); s = -1; } return s; } void vsf_privop_pasv_cleanup(struct vsf_session* p_sess) { if (p_sess->pasv_listen_fd != -1) { vsf_sysutil_close(p_sess->pasv_listen_fd); p_sess->pasv_listen_fd = -1; } } int vsf_privop_pasv_active(struct vsf_session* p_sess) { if (p_sess->pasv_listen_fd != -1) { return 1; } return 0; } unsigned short vsf_privop_pasv_listen(struct vsf_session* p_sess) { static struct vsf_sysutil_sockaddr* s_p_sockaddr; int bind_retries = 10; unsigned short the_port; /* IPPORT_RESERVED */ unsigned short min_port = 1024; unsigned short max_port = 65535; int is_ipv6 = vsf_sysutil_sockaddr_is_ipv6(p_sess->p_local_addr); if (p_sess->pasv_listen_fd != -1) { die("listed fd already active"); } if (tunable_pasv_min_port > min_port && tunable_pasv_min_port <= max_port) { min_port = (unsigned short) tunable_pasv_min_port; } if (tunable_pasv_max_port >= min_port && tunable_pasv_max_port < max_port) { max_port = (unsigned short) tunable_pasv_max_port; } while (--bind_retries) { int retval; double scaled_port; the_port = vsf_sysutil_get_random_byte(); the_port = (unsigned short) (the_port << 8); the_port = (unsigned short) (the_port | vsf_sysutil_get_random_byte()); scaled_port = (double) min_port; scaled_port += ((double) the_port / (double) 65536) * ((double) max_port - min_port + 1); the_port = (unsigned short) scaled_port; if (is_ipv6) { p_sess->pasv_listen_fd = vsf_sysutil_get_ipv6_sock(); } else { p_sess->pasv_listen_fd = vsf_sysutil_get_ipv4_sock(); } vsf_sysutil_activate_reuseaddr(p_sess->pasv_listen_fd); vsf_sysutil_sockaddr_clone(&s_p_sockaddr, p_sess->p_local_addr); vsf_sysutil_sockaddr_set_port(s_p_sockaddr, the_port); retval = vsf_sysutil_bind(p_sess->pasv_listen_fd, s_p_sockaddr); if (!vsf_sysutil_retval_is_error(retval)) { retval = vsf_sysutil_listen(p_sess->pasv_listen_fd, 1); if (!vsf_sysutil_retval_is_error(retval)) { break; } } /* SELinux systems can give you an inopportune EACCES, it seems. */ if (vsf_sysutil_get_error() == kVSFSysUtilErrADDRINUSE || vsf_sysutil_get_error() == kVSFSysUtilErrACCES) { vsf_sysutil_close(p_sess->pasv_listen_fd); p_sess->pasv_listen_fd = -1; continue; } die("vsf_sysutil_bind / listen"); } if (!bind_retries) { die("vsf_sysutil_bind"); } return the_port; } int vsf_privop_accept_pasv(struct vsf_session* p_sess) { struct vsf_sysutil_sockaddr* p_accept_addr = 0; int remote_fd; if (p_sess->pasv_listen_fd == -1) { die("listed fd not active"); } vsf_sysutil_sockaddr_alloc(&p_accept_addr); remote_fd = vsf_sysutil_accept_timeout(p_sess->pasv_listen_fd, p_accept_addr, tunable_accept_timeout); if (vsf_sysutil_retval_is_error(remote_fd)) { vsf_sysutil_sockaddr_clear(&p_accept_addr); return -1; } /* SECURITY: * Reject the connection if it wasn't from the same IP as the * control connection. */ if (!tunable_pasv_promiscuous) { if (!vsf_sysutil_sockaddr_addr_equal(p_sess->p_remote_addr, p_accept_addr)) { vsf_sysutil_close(remote_fd); vsf_sysutil_sockaddr_clear(&p_accept_addr); return -2; } } vsf_sysutil_sockaddr_clear(&p_accept_addr); return remote_fd; } void vsf_privop_do_file_chown(struct vsf_session* p_sess, int fd) { static struct vsf_sysutil_statbuf* s_p_statbuf; vsf_sysutil_fstat(fd, &s_p_statbuf); /* Do nothing if it is already owned by the desired user. */ if (vsf_sysutil_statbuf_get_uid(s_p_statbuf) == p_sess->anon_upload_chown_uid) { return; } /* Drop it like a hot potato unless it's a regular file owned by * the the anonymous ftp user */ if (p_sess->anon_upload_chown_uid == -1 || !vsf_sysutil_statbuf_is_regfile(s_p_statbuf) || (vsf_sysutil_statbuf_get_uid(s_p_statbuf) != p_sess->anon_ftp_uid && vsf_sysutil_statbuf_get_uid(s_p_statbuf) != p_sess->guest_user_uid)) { die("invalid fd in cmd_process_chown"); } /* SECURITY! You need an OS which strips SUID/SGID bits on chown(), * otherwise a compromise of the FTP user will lead to compromise of * the "anon_upload_chown_uid" user (think chmod +s). */ vsf_sysutil_fchown(fd, p_sess->anon_upload_chown_uid, -1); } enum EVSFPrivopLoginResult vsf_privop_do_login(struct vsf_session* p_sess, const struct mystr* p_pass_str) { enum EVSFPrivopLoginResult result = handle_login(p_sess, &p_sess->user_str, p_pass_str); vsf_log_start_entry(p_sess, kVSFLogEntryLogin); if (result == kVSFLoginFail) { vsf_log_do_log(p_sess, 0); if (tunable_delay_failed_login) { vsf_sysutil_sleep((double) tunable_delay_failed_login); } } else { vsf_log_do_log(p_sess, 1); if (tunable_delay_successful_login) { vsf_sysutil_sleep((double) tunable_delay_successful_login); } } return result; } static enum EVSFPrivopLoginResult handle_login(struct vsf_session* p_sess, struct mystr* p_user_str, const struct mystr* p_pass_str) { /* Do not assume PAM can cope with dodgy input, even though it * almost certainly can. */ int anonymous_login = 0; char first_char; unsigned int len = str_getlen(p_user_str); if (len == 0 || len > VSFTP_USERNAME_MAX) { return kVSFLoginFail; } /* Throw out dodgy start characters */ first_char = str_get_char_at(p_user_str, 0); if (!vsf_sysutil_isalnum(first_char) && first_char != '_' && first_char != '.') { return kVSFLoginFail; } /* Throw out non-printable characters and space in username */ if (str_contains_space(p_user_str) || str_contains_unprintable(p_user_str)) { return kVSFLoginFail; } /* Throw out excessive length passwords */ len = str_getlen(p_pass_str); if (len > VSFTP_PASSWORD_MAX) { return kVSFLoginFail; } /* Check for an anonymous login or "real" login */ if (tunable_anonymous_enable) { struct mystr upper_str = INIT_MYSTR; str_copy(&upper_str, p_user_str); str_upper(&upper_str); if (str_equal_text(&upper_str, "FTP") || str_equal_text(&upper_str, "ANONYMOUS")) { anonymous_login = 1; } str_free(&upper_str); } { enum EVSFPrivopLoginResult result = kVSFLoginFail; if (anonymous_login) { result = handle_anonymous_login(p_sess, p_pass_str); } else { if (!tunable_local_enable) { die("unexpected local login in handle_login"); } result = handle_local_login(p_sess, p_user_str, p_pass_str); } return result; } } static enum EVSFPrivopLoginResult handle_anonymous_login(struct vsf_session* p_sess, const struct mystr* p_pass_str) { if (!str_isempty(&p_sess->banned_email_str) && str_contains_line(&p_sess->banned_email_str, p_pass_str)) { return kVSFLoginFail; } if (!str_isempty(&p_sess->email_passwords_str) && (!str_contains_line(&p_sess->email_passwords_str, p_pass_str) || str_isempty(p_pass_str))) { return kVSFLoginFail; } /* Store the anonymous identity string */ str_copy(&p_sess->anon_pass_str, p_pass_str); if (str_isempty(&p_sess->anon_pass_str)) { str_alloc_text(&p_sess->anon_pass_str, "?"); } /* "Fix" any characters which might upset the log processing */ str_replace_char(&p_sess->anon_pass_str, ' ', '_'); str_replace_char(&p_sess->anon_pass_str, '\n', '?'); { struct mystr ftp_username_str = INIT_MYSTR; if (tunable_ftp_username) { str_alloc_text(&ftp_username_str, tunable_ftp_username); } setup_username_globals(p_sess, &ftp_username_str); str_free(&ftp_username_str); } str_free(&p_sess->banned_email_str); str_free(&p_sess->email_passwords_str); return kVSFLoginAnon; } static enum EVSFPrivopLoginResult handle_local_login(struct vsf_session* p_sess, struct mystr* p_user_str, const struct mystr* p_pass_str) { if (!vsf_sysdep_check_auth(p_user_str, p_pass_str, &p_sess->remote_ip_str)) { return kVSFLoginFail; } setup_username_globals(p_sess, p_user_str); return kVSFLoginReal; } static void setup_username_globals(struct vsf_session* p_sess, const struct mystr* p_str) { str_copy(&p_sess->user_str, p_str); if (tunable_setproctitle_enable) { struct mystr prefix_str = INIT_MYSTR; str_copy(&prefix_str, &p_sess->remote_ip_str); str_append_char(&prefix_str, '/'); str_append_str(&prefix_str, p_str); vsf_sysutil_set_proctitle_prefix(&prefix_str); str_free(&prefix_str); } } vsftpd-3.0.3/sysdeputil.h0000644000175000017500000000517511353024213014405 0ustar chrischris#ifndef VSF_SYSDEPUTIL_H #define VSF_SYSDEPUTIL_H #ifndef VSF_FILESIZE_H #include "filesize.h" #endif /* VSF_SYSDEPUTIL_H: * Support for highly system dependent features, and querying for support * or lack thereof * TODO: document functions! */ struct mystr; /* Authentication of local users */ /* Return 0 for fail, 1 for success */ int vsf_sysdep_check_auth(struct mystr* p_user, const struct mystr* p_pass, const struct mystr* p_remote_host); /* Support for fine grained privilege (capabilities) */ int vsf_sysdep_has_capabilities(void); int vsf_sysdep_has_capabilities_as_non_root(void); void vsf_sysdep_keep_capabilities(void); enum ESysdepCapabilities { kCapabilityCAP_CHOWN = 1, kCapabilityCAP_NET_BIND_SERVICE = 2 /* NOTE - next one will be 4, this is a bitfield */ }; void vsf_sysdep_adopt_capabilities(unsigned int caps); /* Support for sendfile(), Linux-like interface. Collapses to a read/write * loop under the covers if the target system lacks support. */ int vsf_sysutil_sendfile(const int out_fd, const int in_fd, filesize_t* p_offset, filesize_t num_send, unsigned int max_chunk); /* Support for changing the process name as reported by the operating system. * A useful status monitor. NOTE - we don't guarantee that this call will * have any effect. */ void vsf_sysutil_setproctitle_init(int argc, const char* argv[]); void vsf_sysutil_setproctitle(const char* p_text); void vsf_sysutil_setproctitle_str(const struct mystr* p_str); void vsf_sysutil_set_proctitle_prefix(const struct mystr* p_str); /* For now, maps read/write private pages. API to be extended.. */ void vsf_sysutil_map_anon_pages_init(void); void* vsf_sysutil_map_anon_pages(unsigned int length); /* File descriptor passing/receiving */ void vsf_sysutil_send_fd(int sock_fd, int send_fd); int vsf_sysutil_recv_fd(int sock_fd); /* If supported, arrange for current process to die when parent dies. */ void vsf_set_die_if_parent_dies(); /* Or a softer version delivering SIGTERM. */ void vsf_set_term_if_parent_dies(); /* If supported, the ability to fork into different secure namespaces (PID * and IPC. Fails back to normal fork() */ int vsf_sysutil_fork_isolate_failok(); /* Same as above, but in addition tries to fork into an empty network * namespace. Falls back to vsf_sysutil_fork_isolate_failok then normal fork(). */ int vsf_sysutil_fork_isolate_all_failok(); /* If supported, the ability to fork into an empty network namespace. * Fails back to normal fork() */ int vsf_sysutil_fork_newnet(); int vsf_sysutil_getpid_nocache(); #endif /* VSF_SYSDEPUTIL_H */ vsftpd-3.0.3/LICENSE0000644000175000017500000000047010750743700013036 0ustar chrischrisSee "COPYING". vsftpd is licensed under version 2 of the GNU GPL. As copyright holder, I give permission for vsftpd to be linked to the OpenSSL libraries. This includes permission for vsftpd binaries to be distributed linked against the OpenSSL libraries. All other obligations under the GPL v2 remain intact.