summaryrefslogtreecommitdiffstats
path: root/install
diff options
context:
space:
mode:
authorRob Crittenden <rcritten@redhat.com>2009-02-02 13:50:53 -0500
committerRob Crittenden <rcritten@redhat.com>2009-02-03 15:29:20 -0500
commit2d7e0de5ea7a2923708006bc58dae4b35b65fe63 (patch)
tree64a3332f7996f0db70d3fd793312da887b34e8cf /install
parent3274577cd608f947d6b07e6dfcbde393edf5a249 (diff)
downloadfreeipa-2d7e0de5ea7a2923708006bc58dae4b35b65fe63.tar.gz
freeipa-2d7e0de5ea7a2923708006bc58dae4b35b65fe63.tar.xz
freeipa-2d7e0de5ea7a2923708006bc58dae4b35b65fe63.zip
Get merged tree into an installalble state.
I have only tested the all, rpms and *clean targets directly. install may work but the rpm moves a lot of things around for us. The Apache configuration file isn't in its final state but it works with the new mod_python configuration.
Diffstat (limited to 'install')
-rw-r--r--install/Makefile.am50
-rwxr-xr-xinstall/autogen.sh202
-rw-r--r--install/conf/Makefile.am15
-rw-r--r--install/conf/ipa.conf15
-rw-r--r--install/configure.ac43
-rw-r--r--install/html/Makefile.am15
-rw-r--r--install/tools/Makefile.am11
-rwxr-xr-x[-rw-r--r--]install/tools/ipa-server-install51
-rw-r--r--install/tools/man/Makefile.am2
-rw-r--r--install/tools/man/ipa_webgui.837
10 files changed, 363 insertions, 78 deletions
diff --git a/install/Makefile.am b/install/Makefile.am
new file mode 100644
index 000000000..e57818cdc
--- /dev/null
+++ b/install/Makefile.am
@@ -0,0 +1,50 @@
+# This file will be processed with automake-1.7 to create Makefile.in
+#
+AUTOMAKE_OPTIONS = 1.7
+
+NULL =
+
+SUBDIRS = \
+ conf \
+ html \
+ share \
+ tools \
+ updates \
+ $(NULL)
+
+install-exec-local:
+ mkdir -p $(DESTDIR)$(localstatedir)/lib/ipa/sysrestore
+ chmod 700 $(DESTDIR)$(localstatedir)/lib/ipa/sysrestore
+ mkdir -p $(DESTDIR)$(localstatedir)/cache/ipa/sessions
+ chmod 700 $(DESTDIR)$(localstatedir)/cache/ipa/sessions
+
+uninstall-local:
+ -rmdir $(DESTDIR)$(localstatedir)/lib/ipa/sysrestore
+ -rmdir $(DESTDIR)$(localstatedir)/lib/ipa
+ -rmdir $(DESTDIR)$(localstatedir)/cache/ipa/sessions
+ -rmdir $(DESTDIR)$(localstatedir)/cache/ipa
+
+DISTCLEANFILES = \
+ $(NULL)
+
+MAINTAINERCLEANFILES = \
+ *~ \
+ intltool-*.in \
+ compile \
+ configure \
+ COPYING \
+ INSTALL \
+ install-sh \
+ missing \
+ mkinstalldirs \
+ config.guess \
+ ltmain.sh \
+ config.sub \
+ depcomp \
+ Makefile.in \
+ config.h.* \
+ aclocal.m4 \
+ version.m4 \
+ ipa-client.spec \
+ py-compile \
+ $(NULL)
diff --git a/install/autogen.sh b/install/autogen.sh
new file mode 100755
index 000000000..d0ef1806a
--- /dev/null
+++ b/install/autogen.sh
@@ -0,0 +1,202 @@
+#!/bin/sh
+# Run this to generate all the initial makefiles, etc.
+set -e
+
+PACKAGE=freeipa-server
+
+LIBTOOLIZE=${LIBTOOLIZE-libtoolize}
+LIBTOOLIZE_FLAGS="--copy --force"
+AUTOHEADER=${AUTOHEADER-autoheader}
+AUTOMAKE_FLAGS="--add-missing --gnu"
+AUTOCONF=${AUTOCONF-autoconf}
+
+# automake 1.8 requires autoconf 2.58
+# automake 1.7 requires autoconf 2.54
+automake_min_vers=1.7
+aclocal_min_vers=$automake_min_vers
+autoconf_min_vers=2.54
+libtoolize_min_vers=1.4
+
+# The awk-based string->number conversion we use needs a C locale to work
+# as expected. Setting LC_ALL overrides whether the user set LC_ALL,
+# LC_NUMERIC, or LANG.
+LC_ALL=C
+
+ARGV0=$0
+
+# Allow invocation from a separate build directory; in that case, we change
+# to the source directory to run the auto*, then change back before running configure
+srcdir=`dirname $ARGV0`
+test -z "$srcdir" && srcdir=.
+
+ORIGDIR=`pwd`
+
+cd $srcdir
+
+# Usage:
+# compare_versions MIN_VERSION ACTUAL_VERSION
+# returns true if ACTUAL_VERSION >= MIN_VERSION
+compare_versions() {
+ ch_min_version=$1
+ ch_actual_version=$2
+ ch_status=0
+ IFS="${IFS= }"; ch_save_IFS="$IFS"; IFS="."
+ set $ch_actual_version
+ for ch_min in $ch_min_version; do
+ ch_cur=`echo $1 | sed 's/[^0-9].*$//'`; shift # remove letter suffixes
+ if [ -z "$ch_min" ]; then break; fi
+ if [ -z "$ch_cur" ]; then ch_status=1; break; fi
+ if [ $ch_cur -gt $ch_min ]; then break; fi
+ if [ $ch_cur -lt $ch_min ]; then ch_status=1; break; fi
+ done
+ IFS="$ch_save_IFS"
+ return $ch_status
+}
+
+if ($AUTOCONF --version) < /dev/null > /dev/null 2>&1 ; then
+ if ($AUTOCONF --version | head -n 1 | awk 'NR==1 { if( $(NF) >= '$autoconf_min_vers') \
+ exit 1; exit 0; }');
+ then
+ echo "$ARGV0: ERROR: \`$AUTOCONF' is too old."
+ $AUTOCONF --version
+ echo " (version $autoconf_min_vers or newer is required)"
+ DIE="yes"
+ fi
+else
+ echo $AUTOCONF: command not found
+ echo
+ echo "$ARGV0: ERROR: You must have \`autoconf' installed to compile $PACKAGE."
+ echo " (version $autoconf_min_vers or newer is required)"
+ DIE="yes"
+fi
+
+#
+# Hunt for an appropriate version of automake and aclocal; we can't
+# assume that 'automake' is necessarily the most recent installed version
+#
+# We check automake first to allow it to be a newer version than we know about.
+#
+if test x"$AUTOMAKE" = x || test x"$ACLOCAL" = x ; then
+ am_ver=""
+ for ver in "" "-1.9" "-1.8" "-1.7" ; do
+ am="automake$ver"
+ if ($am --version) < /dev/null > /dev/null 2>&1 ; then
+ if ($am --version | head -n 1 | awk 'NR==1 { if( $(NF) >= '$automake_min_vers') \
+ exit 1; exit 0; }'); then : ; else
+ am_ver=$ver
+ break;
+ fi
+ fi
+ done
+
+ AUTOMAKE=${AUTOMAKE-automake$am_ver}
+ ACLOCAL=${ACLOCAL-aclocal$am_ver}
+fi
+
+#
+# Now repeat the tests with the copies we decided upon and error out if they
+# aren't sufficiently new.
+#
+if ($AUTOMAKE --version) < /dev/null > /dev/null 2>&1 ; then
+ automake_actual_version=`$AUTOMAKE --version | head -n 1 | \
+ sed 's/^.*[ ]\([0-9.]*[a-z]*\).*$/\1/'`
+ if ! compare_versions $automake_min_vers $automake_actual_version; then
+ echo "$ARGV0: ERROR: \`$AUTOMAKE' is too old."
+ $AUTOMAKE --version
+ echo " (version $automake_min_vers or newer is required)"
+ DIE="yes"
+ fi
+ if ($ACLOCAL --version) < /dev/null > /dev/null 2>&1; then
+ aclocal_actual_version=`$ACLOCAL --version | head -n 1 | \
+ sed 's/^.*[ ]\([0-9.]*[a-z]*\).*$/\1/'`
+
+ if ! compare_versions $aclocal_min_vers $aclocal_actual_version; then
+ echo "$ARGV0: ERROR: \`$ACLOCAL' is too old."
+ $ACLOCAL --version
+ echo " (version $aclocal_min_vers or newer is required)"
+ DIE="yes"
+ fi
+ else
+ echo $ACLOCAL: command not found
+ echo
+ echo "$ARGV0: ERROR: Missing \`$ACLOCAL'"
+ echo " The version of $AUTOMAKE installed doesn't appear recent enough."
+ DIE="yes"
+ fi
+else
+ echo $AUTOMAKE: command not found
+ echo
+ echo "$ARGV0: ERROR: You must have \`automake' installed to compile $PACKAGE."
+ echo " (version $automake_min_vers or newer is required)"
+ DIE="yes"
+fi
+
+if ($LIBTOOLIZE --version) < /dev/null > /dev/null 2>&1 ; then
+ if ($LIBTOOLIZE --version | awk 'NR==1 { if( $4 >= '$libtoolize_min_vers') \
+ exit 1; exit 0; }');
+ then
+ echo "$ARGV0: ERROR: \`$LIBTOOLIZE' is too old."
+ echo " (version $libtoolize_min_vers or newer is required)"
+ DIE="yes"
+ fi
+else
+ echo $LIBTOOLIZE: command not found
+ echo
+ echo "$ARGV0: ERROR: You must have \`libtoolize' installed to compile $PACKAGE."
+ echo " (version $libtoolize_min_vers or newer is required)"
+ DIE="yes"
+fi
+
+if test -z "$ACLOCAL_FLAGS"; then
+ acdir=`$ACLOCAL --print-ac-dir`
+ if [ ! -f $acdir/pkg.m4 ]; then
+ echo "$ARGV0: Error: Could not find pkg-config macros."
+ echo " (Looked in $acdir/pkg.m4)"
+ echo " If pkg.m4 is available in /another/directory, please set"
+ echo " ACLOCAL_FLAGS=\"-I /another/directory\""
+ echo " Otherwise, please install pkg-config."
+ echo ""
+ echo "pkg-config is available from:"
+ echo "http://www.freedesktop.org/software/pkgconfig/"
+ DIE=yes
+ fi
+fi
+
+if test "X$DIE" != X; then
+ exit 1
+fi
+
+
+if test -z "$*"; then
+ echo "$ARGV0: Note: \`./configure' will be run with no arguments."
+ echo " If you wish to pass any to it, please specify them on the"
+ echo " \`$0' command line."
+ echo
+fi
+
+do_cmd() {
+ echo "$ARGV0: running \`$@'"
+ $@
+}
+
+# I don't want autotools dictating what files I have
+touch NEWS README AUTHORS ChangeLog
+
+do_cmd $LIBTOOLIZE $LIBTOOLIZE_FLAGS
+
+do_cmd $ACLOCAL $ACLOCAL_FLAGS
+
+do_cmd $AUTOHEADER
+
+do_cmd $AUTOMAKE $AUTOMAKE_FLAGS
+
+do_cmd $AUTOCONF
+
+# I don't want autotools dictating what files I have
+touch NEWS README AUTHORS ChangeLog
+
+cd $ORIGDIR || exit 1
+
+rm -f config.cache
+
+do_cmd $srcdir/configure --cache-file=config.cache --disable-static --enable-maintainer-mode --enable-gtk-doc ${1+"$@"} && echo "Now type \`make' to compile" || exit 1
diff --git a/install/conf/Makefile.am b/install/conf/Makefile.am
new file mode 100644
index 000000000..e00ad618f
--- /dev/null
+++ b/install/conf/Makefile.am
@@ -0,0 +1,15 @@
+NULL =
+
+appdir = $(IPA_DATA_DIR)
+app_DATA = \
+ ipa.conf \
+ ipa-rewrite.conf \
+ $(NULL)
+
+EXTRA_DIST = \
+ $(app_DATA) \
+ $(NULL)
+
+MAINTAINERCLEANFILES = \
+ *~ \
+ Makefile.in
diff --git a/install/conf/ipa.conf b/install/conf/ipa.conf
index 85b4543af..9656fdf35 100644
--- a/install/conf/ipa.conf
+++ b/install/conf/ipa.conf
@@ -4,6 +4,7 @@
# LoadModule auth_kerb_module modules/mod_auth_kerb.so
ProxyRequests Off
+PythonImport ipaserver main_interpreter
# ipa-rewrite.conf is loaded separately
@@ -34,16 +35,13 @@ AddType application/java-archive jar
ProxyPass /ipa/ui http://localhost:8080/ipa/ui
ProxyPassReverse /ipa/ui http://localhost:8080/ipa/ui
-# Configure the XML-RPC service
-Alias /ipa/xml "/usr/share/ipa/ipaserver/XMLRPC"
-
# This is where we redirect on failed auth
Alias /ipa/errors "/usr/share/ipa/html"
# For the MIT Windows config files
Alias /ipa/config "/usr/share/ipa/html"
-<Directory "/usr/share/ipa/ipaserver">
+<Location "/ipa/xml">
AuthType Kerberos
AuthName "Kerberos Login"
KrbMethodNegotiate on
@@ -55,16 +53,17 @@ Alias /ipa/config "/usr/share/ipa/html"
Require valid-user
ErrorDocument 401 /ipa/errors/unauthorized.html
- SetHandler mod_python
- PythonHandler ipaxmlrpc
-
+ SetHandler python-program
+ PythonInterpreter main_interpreter
+ PythonHandler ipaserver::xmlrpc
+
PythonDebug Off
PythonOption IPADebug Off
# this is pointless to use since it would just reload ipaxmlrpc.py
PythonAutoReload Off
-</Directory>
+</Location>
# Do no authentication on the directory that contains error messages
<Directory "/usr/share/ipa/html">
diff --git a/install/configure.ac b/install/configure.ac
new file mode 100644
index 000000000..7f96812f0
--- /dev/null
+++ b/install/configure.ac
@@ -0,0 +1,43 @@
+AC_PREREQ(2.59)
+m4_include(../version.m4)
+AC_INIT([ipa-server],
+ IPA_VERSION,
+ [https://hosted.fedoraproject.org/projects/freeipa/newticket])
+
+#AC_CONFIG_SRCDIR([ipaserver/ipaldap.py])
+AC_CONFIG_HEADERS([config.h])
+
+AM_INIT_AUTOMAKE
+
+AM_MAINTAINER_MODE
+#AC_PROG_CC
+#AC_STDC_HEADERS
+#AC_DISABLE_STATIC
+#AC_PROG_LIBTOOL
+
+#AC_HEADER_STDC
+
+AC_SUBST(VERSION)
+
+dnl ---------------------------------------------------------------------------
+dnl - Set the data install directory since we don't use pkgdatadir
+dnl ---------------------------------------------------------------------------
+
+IPA_DATA_DIR="$datadir/ipa"
+IPA_SYSCONF_DIR="$sysconfdir/ipa"
+AC_SUBST(IPA_DATA_DIR)
+AC_SUBST(IPA_SYSCONF_DIR)
+
+# Files
+
+AC_CONFIG_FILES([
+ Makefile
+ conf/Makefile
+ html/Makefile
+ share/Makefile
+ tools/Makefile
+ tools/man/Makefile
+ updates/Makefile
+])
+
+AC_OUTPUT
diff --git a/install/html/Makefile.am b/install/html/Makefile.am
new file mode 100644
index 000000000..df2e9a5e7
--- /dev/null
+++ b/install/html/Makefile.am
@@ -0,0 +1,15 @@
+NULL =
+
+appdir = $(IPA_SYSCONF_DIR)/html
+app_DATA = \
+ ssbrowser.html \
+ unauthorized.html \
+ $(NULL)
+
+EXTRA_DIST = \
+ $(app_DATA) \
+ $(NULL)
+
+MAINTAINERCLEANFILES = \
+ *~ \
+ Makefile.in
diff --git a/install/tools/Makefile.am b/install/tools/Makefile.am
index 3f5661754..750ab6417 100644
--- a/install/tools/Makefile.am
+++ b/install/tools/Makefile.am
@@ -1,9 +1,8 @@
NULL =
-SUBDIRS = \
- share \
- updates \
- $(NULL)
+SUBDIRS = \
+ man \
+ $(NULL)
sbin_SCRIPTS = \
ipa-server-install \
@@ -12,6 +11,10 @@ sbin_SCRIPTS = \
ipa-replica-manage \
ipa-server-certinstall \
ipactl \
+ ipa-compat-manage \
+ ipa-fix-CVE-2008-3274 \
+ ipa-ldap-updater \
+ ipa-upgradeconfig \
$(NULL)
EXTRA_DIST = \
diff --git a/install/tools/ipa-server-install b/install/tools/ipa-server-install
index c9d5c5bf3..70e74ac51 100644..100755
--- a/install/tools/ipa-server-install
+++ b/install/tools/ipa-server-install
@@ -37,15 +37,15 @@ import glob
import traceback
from optparse import OptionParser
-import ipaserver.dsinstance
-import ipaserver.krbinstance
-import ipaserver.bindinstance
-import ipaserver.httpinstance
-import ipaserver.ntpinstance
+from ipaserver.install import dsinstance
+from ipaserver.install import krbinstance
+from ipaserver.install import bindinstance
+from ipaserver.install import httpinstance
+from ipaserver.install import ntpinstance
-from ipaserver import service
+from ipaserver.install import service
from ipa import version
-from ipaserver.installutils import *
+from ipaserver.install.installutils import *
from ipa import sysrestore
from ipa.ipautil import *
@@ -119,7 +119,7 @@ def signal_handler(signum, frame):
print "Removing configuration for %s instance" % ds.serverid
ds.stop()
if ds.serverid:
- ipaserver.dsinstance.erase_ds_instance_data (ds.serverid)
+ dsinstance.erase_ds_instance_data (ds.serverid)
sys.exit(1)
def read_host_name(host_default,no_host_dns=False):
@@ -269,7 +269,7 @@ def read_admin_password():
return admin_password
def check_dirsrv(unattended):
- serverids = ipaserver.dsinstance.check_existing_installation()
+ serverids = dsinstance.check_existing_installation()
if serverids:
print ""
print "An existing Directory Server has been detected."
@@ -285,9 +285,9 @@ def check_dirsrv(unattended):
pass
for serverid in serverids:
- ipaserver.dsinstance.erase_ds_instance_data(serverid)
+ dsinstance.erase_ds_instance_data(serverid)
- (ds_unsecure, ds_secure) = ipaserver.dsinstance.check_ports()
+ (ds_unsecure, ds_secure) = dsinstance.check_ports()
if not ds_unsecure or not ds_secure:
print "IPA requires ports 389 and 636 for the Directory Server."
print "These are currently in use:"
@@ -305,12 +305,11 @@ def uninstall():
print "ipa-client-install returned: " + str(e)
pass
- ipaserver.ntpinstance.NTPInstance(fstore).uninstall()
- ipaserver.bindinstance.BindInstance(fstore).uninstall()
- ipaserver.httpinstance.WebGuiInstance().uninstall()
- ipaserver.httpinstance.HTTPInstance(fstore).uninstall()
- ipaserver.krbinstance.KrbInstance(fstore).uninstall()
- ipaserver.dsinstance.DsInstance().uninstall()
+ ntpinstance.NTPInstance(fstore).uninstall()
+ bindinstance.BindInstance(fstore).uninstall()
+ httpinstance.HTTPInstance(fstore).uninstall()
+ krbinstance.KrbInstance(fstore).uninstall()
+ dsinstance.DsInstance().uninstall()
fstore.restore_all_files()
return 0
@@ -487,7 +486,7 @@ def main():
# Configure ntpd
if options.conf_ntp:
- ntp = ipaserver.ntpinstance.NTPInstance(fstore)
+ ntp = ntpinstance.NTPInstance(fstore)
ntp.create_instance()
if options.dirsrv_pin:
@@ -496,7 +495,7 @@ def main():
os.close(pw_fd)
# Create a directory server instance
- ds = ipaserver.dsinstance.DsInstance()
+ ds = dsinstance.DsInstance()
if options.dirsrv_pkcs12:
pkcs12_info = (options.dirsrv_pkcs12, pw_name)
ds.create_instance(ds_user, realm_name, host_name, domain_name, dm_password, pkcs12_info)
@@ -505,7 +504,7 @@ def main():
ds.create_instance(ds_user, realm_name, host_name, domain_name, dm_password)
# Create a kerberos instance
- krb = ipaserver.krbinstance.KrbInstance(fstore)
+ krb = krbinstance.KrbInstance(fstore)
krb.create_instance(ds_user, realm_name, host_name, domain_name, dm_password, master_password)
# Create a HTTP instance
@@ -515,7 +514,7 @@ def main():
os.write(pw_fd, options.http_pin)
os.close(pw_fd)
- http = ipaserver.httpinstance.HTTPInstance(fstore)
+ http = httpinstance.HTTPInstance(fstore)
if options.http_pkcs12:
pkcs12_info = (options.http_pkcs12, pw_name)
http.create_instance(realm_name, host_name, domain_name, autoconfig=False, pkcs12_info=pkcs12_info)
@@ -532,11 +531,7 @@ def main():
fd.write("domain=" + domain_name + "\n")
fd.close()
- # Create a Web Gui instance
- webgui = ipaserver.httpinstance.WebGuiInstance()
- webgui.create_instance()
-
- bind = ipaserver.bindinstance.BindInstance(fstore)
+ bind = bindinstance.BindInstance(fstore)
bind.setup(host_name, ip_address, realm_name, domain_name)
if options.setup_bind:
bind.create_instance()
@@ -594,8 +589,8 @@ def main():
print ""
if not options.dirsrv_pkcs12:
- print "Be sure to back up the CA certificate stored in " + ipaserver.dsinstance.config_dirname(ds.serverid) + "cacert.p12"
- print "The password for this file is in " + ipaserver.dsinstance.config_dirname(ds.serverid) + "pwdfile.txt"
+ print "Be sure to back up the CA certificate stored in " + dsinstance.config_dirname(ds.serverid) + "cacert.p12"
+ print "The password for this file is in " + dsinstance.config_dirname(ds.serverid) + "pwdfile.txt"
else:
print "In order for Firefox autoconfiguration to work you will need to"
print "use a SSL signing certificate. See the IPA documentation for more details."
diff --git a/install/tools/man/Makefile.am b/install/tools/man/Makefile.am
index 244b06b8d..b2c3fa360 100644
--- a/install/tools/man/Makefile.am
+++ b/install/tools/man/Makefile.am
@@ -16,7 +16,7 @@ man1_MANS = \
man8_MANS = \
ipactl.8 \
ipa_kpasswd.8 \
- ipa_webgui.8
+ $(NULL)
install-data-hook:
@for i in $(man1_MANS) ; do gzip -f $(DESTDIR)$(man1dir)/$$i ; done
diff --git a/install/tools/man/ipa_webgui.8 b/install/tools/man/ipa_webgui.8
deleted file mode 100644
index 20545363f..000000000
--- a/install/tools/man/ipa_webgui.8
+++ /dev/null
@@ -1,37 +0,0 @@
-.\" A man page for ipa_webgui
-.\" Copyright (C) 2008 Red Hat, Inc.
-.\"
-.\" This is free software; you can redistribute it and/or modify it under
-.\" the terms of the GNU Library General Public License as published by
-.\" the Free Software Foundation; version 2 only
-.\"
-.\" 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 Library General Public
-.\" License along with this program; if not, write to the Free Software
-.\" Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-.\"
-.\" Author: Rob Crittenden <rcritten@redhat.com>
-.\"
-.TH "ipa_webgui" "8" "Mar 14 2008" "freeipa" ""
-.SH "NAME"
-ipa_webgui \- Start the IPA Web User Interface
-.SH "SYNOPSIS"
-ipa_webgui [\fIOPTION\fR]...
-
-.SH "DESCRIPTION"
-Used to start the TurboGears web user interface for IPA
-.SH "OPTIONS"
-.TP
-\fB\-f\fR, \fB\-\-foreground\fR
-Remain in the foreground instead of becoming a daemon.
-.TP
-\fB\-d\fR, \fB\-\-debug\fR
-.TP
-Increase the amount of logging and print it to stdout instead of logging to /var/log/ipa_error.log
-
-.SH "EXIT STATUS"
-1 if an error occurred