Monthly Archives: December 2013

Set up FTP server on CentOS 6

Introduction

The purpose of this post is to document the steps to set up FTP server on CentOS 6. The FTP server software used is the default FTP server daemon for CentOS 6 at the time of writing, which is vsftpd.  The FTP server is run behind an iptables firewall and SELinux, so this post will include iptables and SELinux settings that are needed.

Install FTP server software

To start things off, the FTP server software is installed.

[hazrul@server ~]$ sudo yum grouplist | grep -i ftp
   FTP server
[hazrul@server ~]$ sudo yum groupinstall "FTP server"

Check FTP server configuration file

The vsftpd config files are located in /etc/vsftpd.  The main configuration file vsftpd.conf is edited to check the configuration, and modified where necessary.

[hazrul@server ~]$ sudo vi /etc/vsftpd/vsftpd.conf
# Example config file /etc/vsftpd/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
anonymous_enable=NO
#
# 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
#
# The target log file can be vsftpd_log_file or xferlog_file.
# This depends on setting xferlog_std_format parameter
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
#
# The name of log file when xferlog_enable=YES and xferlog_std_format=YES
# WARNING - changing this filename affects /etc/logrotate.d/vsftpd.log
#xferlog_file=/var/log/xferlog
#
# Switches between logging into vsftpd_log_file and xferlog_file files.
# NO writes to vsftpd_log_file, YES to xferlog_file
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().
#chroot_local_user=YES
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

pam_service_name=vsftpd
userlist_enable=YES
userlist_DENY=NO
tcp_wrappers=YES

The default configuration provided by CentOS 6 allows the following users to log in:-

  1. Anonymous users – Read-only access to /var/ftp and its subdirectories.
  2. Local users – Read-write access to the whole filesystem, governed only by file permission.

However, if SELinux is turned on, the default SELinux configuration will stop any writes from happening.

The default vsftpd configuration is a bit loose for my liking.  I prefer not to have anonymous users logging into the server, and I prefer to have the local users confined to their home directories.  Therefore, the configuration was modified as highlighted above.

To deny anonymous users, anonymous_enable is set to NO.

To confine local users to their home directories, chroot_local_user is set to YES.

An extra line is also added to the configuration file which is userlist_deny=NO.  By default, vsftpd will check for the file /etc/vsftpd/user_list, and deny access to the users listed in that file.  By setting userlist_deny=NO, ONLY users listed in that file is allowed access.

Therefore, the initial user list file will have to be changed.

[hazrul@server ~]$ sudo cat /etc/vsftpd/user_list
# vsftpd userlist
# If userlist_deny=NO, only allow users in this file
# If userlist_deny=YES (default), never allow users in this file, and
# do not even prompt for a password.
# Note that the default vsftpd pam config also checks /etc/vsftpd/ftpusers
# for users that are denied.
root
bin
daemon
adm
lp
sync
shutdown
halt
mail
news
uucp
operator
games
nobody
[hazrul@server ~]$ sudo mv /etc/vsftpd/user_list /etc/vsftpd/user_list.bak
[hazrul@server ~]$ sudo touch /etc/vsftpd/user_list

The user list is now empty, and must be later filled with the usernames of the users that are to be allowed FTP access.

Configure SELinux to work with vsftpd

SELinux controls vsftpd on a tight leash.  To make the FTP server work as intended, at least one SELinux boolean needs to be modified.  First, all the booleans related to FTP is listed.

[hazrul@server ~]$ sudo getsebool -a | grep -i ftp
allow_ftpd_anon_write --> off
allow_ftpd_full_access --> off
allow_ftpd_use_cifs --> off
allow_ftpd_use_nfs --> off
ftp_home_dir --> off
ftpd_connect_db --> off
ftpd_use_fusefs --> off
ftpd_use_passive_mode --> off
httpd_enable_ftp_server --> off
tftp_anon_write --> off
tftp_use_cifs --> off
tftp_use_nfs --> off

In this particular setup, the boolean that needs to be changed is ftp_home_dir.  This will allow users to read and write to their home directories located in /home/username.

[hazrul@server ~]$ sudo setsebool -P ftp_home_dir on
[hazrul@server ~]$ sudo getsebool ftp_home_dir
ftp_home_dir --> on

Start and configure the vsftpd service

Next, the FTP server is started, and configured to start at boot.

[hazrul@server ~]$ sudo service vsftpd start
Starting vsftpd for vsftpd:                                [  OK  ]
[hazrul@server ~]$ sudo chkconfig vsftpd on

Configure iptables firewall to allow FTP

FTP is an interesting protocol; it requires two TCP connections: one for command, and another for data.

In active mode, the client initiates the command TCP connection on port 21 of the FTP server while providing a port number to the server for the data connection.  The FTP server then connects to the provided port from port 20 of the server.

In passive mode, similarly the client initiates the command TCP connection on port 21 of the server.  However, instead of providing a port number, the client then sends a PASV command indicating the intention to use passive mode.  The server receives the PASV command and opens up an unregistered port for the client to connect to for the data connection.  The server then sends the port number to the client for the client to initiate the connection.

A more detailed explanation of this can be found here.

To configure the iptables firewall for FTP is quite straight-forward for active mode.  Just open up incoming connections on port 21, and allow outgoing connections on port 20.

Passive mode is a bit tricky.  For the command connection, similarly port 21 is opened for incoming connections.  However, for the data connection, the port number that is needed to be opened changes from one session to another.  To open up all the unregistered port is not an acceptable solution security-wise as that is virtually one step below disabling the firewall altogether.

Fortunately, there exists an iptables module called ip_conntrack_ftp that uses the connection tracking feature of iptables to automatically open up ports for passive mode. All that is needed to be done is to load this module when iptables is started.

To recap, the following configuration needs to be performed on iptables to allow vsftpd to work properly:

  1. Open up incoming port 21.
  2. Open up outgoing port 20 (unnecessary if using the default iptables rules provided by CentOS which already opened all outgoing ports).
  3. Load the ip_conntrack_ftp module.

To open port 21, the following command is used:

[hazrul@server ~]$ sudo iptables -I INPUT 5 -m state --state NEW -p tcp --dport 21 -j ACCEPT
[hazrul@server ~]$ sudo service iptables save
iptables: Saving firewall rules to /etc/sysconfig/iptables:[  OK  ]

To load the ip_conntrack_ftp module, the iptables configuration file is modified to include the module, and the iptables service is restarted.

[hazrul@server ~]$ sudo vi /etc/sysconfig/iptables-config
# Load additional iptables modules (nat helpers)
#   Default: -none-
# Space separated list of nat helpers (e.g. 'ip_nat_ftp ip_nat_irc'), which
# are loaded after the firewall rules are applied. Options for the helpers are
# stored in /etc/modprobe.conf.
IPTABLES_MODULES="ip_conntrack_ftp"
[hazrul@server ~]$ sudo service iptables restart
iptables: Setting chains to policy ACCEPT: filter          [  OK  ]
iptables: Flushing firewall rules:                         [  OK  ]
iptables: Unloading modules:                               [  OK  ]
iptables: Applying firewall rules:                         [  OK  ]
iptables: Loading additional modules: ip_conntrack_ftp     [  OK  ]

Create a user and test FTP access

The FTP server is now properly set up and can be tested.

Begin by creating a new user.

[hazrul@server ~]$ sudo useradd ftpuser
[hazrul@server ~]$ sudo passwd ftpuser
Changing password for user ftpuser.
New password:<password>
Retype new password:<password>
passwd: all authentication tokens updated successfully.

Add the user to the vsftpd user list.

[hazrul@server ~]$ sudo bash -c "echo ftpuser >> /etc/vsftpd/user_list"

Connect to the server using an FTP client.  The connection should succeed, and transfer of files to and from the server should now work as expected.

That concludes this post on how to set up FTP server on CentOS 6.

Create init script in CentOS 6

The purpose of this post is to document how to create init script in CentOS 6 to be used with the usual service management commands such as service and chkconfig.

Introduction

After being somewhat dissatisfied with the service script provided by GlassFish, I decided to learn how to create my own service script to start, stop, and view the status of non-packaged applications.

Learn Shell Scripting

I began by looking at other service scripts that has been already provided by CentOS such as /etc/init.d/httpd and /etc/init.d/iptables for ideas.

To help understand the scripts, I had to start learning shell scripting.  A very good tutorial can be found here.

Writing the script

The skeleton script

There is a file inside CentOS that explains a bit about how to write init scripts.  The path to the file is /usr/share/doc/initscripts-*/sysvinitfiles.  You can open this file in a text editor, and have a read.

Included in this file is a base outline of an init script that we can base our script on.  The following is the content of the outline:

#!/bin/bash
#
#       /etc/rc.d/init.d/<servicename>
#
#       <description of the *service*>
#       <any general comments about this init script>
#
# <tags -- see below for tag definitions.  *Every line* from the top
#  of the file to the end of the tags section must begin with a #
#  character.  After the tags section, there should be a blank line.
#  This keeps normal comments in the rest of the file from being
#  mistaken for tags, should they happen to fit the pattern.>

# Source function library.
. /etc/init.d/functions

<define any local shell functions used by the code that follows>

start() {
        echo -n "Starting <servicename>: "
        <start daemons, perhaps with the daemon function>
        touch /var/lock/subsys/<servicename>
        return <return code of starting daemon>
}

stop() {
        echo -n "Shutting down <servicename>: "
        <stop daemons, perhaps with the killproc function>
        rm -f /var/lock/subsys/<servicename>
        return <return code of stopping daemon>
}

case "$1" in
    start)
        start
        ;;
    stop)
        stop
        ;;
    status)
        <report the status of the daemons in free-form format,
        perhaps with the status function>
        ;;
    restart)
        stop
        start
        ;;
    reload)
        <cause the service configuration to be reread, either with
        kill -HUP or by restarting the daemons, in a manner similar
        to restart above>
        ;;
    condrestart)
        <Restarts the servce if it is already running. For example:>
        [ -f /var/lock/subsys/<service> ] && restart || :
    probe)
        <optional.  If it exists, then it should determine whether
        or not the service needs to be restarted or reloaded (or
        whatever) in order to activate any changes in the configuration
        scripts.  It should print out a list of commands to give to
        $0; see the description under the probe tag below.>
        ;;
    *)
        echo "Usage: <servicename> {start|stop|status|reload|restart[|probe]"
        exit 1
        ;;
esac
exit $?

Create init script by modifying the skeleton script

Using the skeleton script as a base, and comparing it with other established init scripts, I have come up with the following for GlassFish 4:

#!/bin/bash
#
# glassfish4    GlassFish Server Open Source Edition 4.0
#
# chkconfig: 345 70 30
# description: GlassFish Server is a Java EE Application Server Platform
# processname: glassfish4

# Source function library.
. /etc/init.d/functions

RETVAL=0
prog="glassfish4"
LOCKFILE=/var/lock/subsys/$prog

# Declare variables for GlassFish Server
GLASSFISH_DIR=/home/gfish/glassfish4
GLASSFISH_USER=gfish
ASADMIN=$GLASSFISH_DIR/bin/asadmin
DOMAIN=domain1

start() {
        echo -n "Starting $prog: "
        daemon --user $GLASSFISH_USER $ASADMIN start-domain $DOMAIN
        RETVAL=$?
        [ $RETVAL -eq 0 ] && touch $LOCKFILE
        echo
        return $RETVAL
}

stop() {
        echo -n "Shutting down $prog: "
        $ASADMIN stop-domain domain1 && success || failure
        RETVAL=$?
        [ $RETVAL -eq 0 ] && rm -f $LOCKFILE
        echo
        return $RETVAL
}

status() {
        echo -n "Checking $prog status: "
        $ASADMIN list-domains | grep $DOMAIN
        RETVAL=$?
        return $RETVAL
}

case "$1" in
    start)
        start
        ;;
    stop)
        stop
        ;;
    status)
        status
        ;;
    restart)
        stop
        start
        ;;
    *)
        echo "Usage: $prog {start|stop|status|restart}"
        exit 1
        ;;
esac
exit $RETVAL

Using the script

Add the script to chkconfig

The script is placed inside the /etc/init.d/ directory, and given the name glassfish4.

Then, the script is added to the list using the chkconfig command.

[root@server ~]# chkconfig --add glassfish4
[root@server ~]# chkconfig --list glassfish4
glassfish4      0:off   1:off   2:off   3:on    4:on    5:on    6:off

Use the start operand

[root@server ~]# service glassfish4 start
Starting glassfish4: Waiting for domain1 to start .........................
Successfully started the domain : domain1
domain  Location: /home/gfish/glassfish4/glassfish/domains/domain1
Log File: /home/gfish/glassfish4/glassfish/domains/domain1/logs/server.log
Admin Port: 4848
Command start-domain executed successfully.
                                                           [  OK  ]

Use the stop operand

[root@server ~]# service glassfish4 stop
Shutting down glassfish4: Waiting for the domain to stop ......
Command stop-domain executed successfully.
                                                           [  OK  ]

Use the restart operand

[root@server ~]# service glassfish4 restart
Shutting down glassfish4: Waiting for the domain to stop ..
Command stop-domain executed successfully.
                                                           [  OK  ]
Starting glassfish4: Waiting for domain1 to start ......................
Successfully started the domain : domain1
domain  Location: /home/gfish/glassfish4/glassfish/domains/domain1
Log File: /home/gfish/glassfish4/glassfish/domains/domain1/logs/server.log
Admin Port: 4848
Command start-domain executed successfully.
                                                           [  OK  ]

Use the status operand

[root@server ~]# service glassfish4 status
Checking glassfish4 status: domain1 running

Use any other operands

[root@server ~]# service glassfish4 lalala
Usage: glassfish4 {start|stop|status|restart}