From 2cd108304db97dc69917b3327492c357b4b7b967 Mon Sep 17 00:00:00 2001 From: "Todd C. Miller" Date: Wed, 16 Jun 2010 11:17:02 -0400 Subject: [PATCH] Add Linux audit support. --- INSTALL | 4 ++ MANIFEST | 2 + WHATSNEW | 3 ++ config.h.in | 5 +- configure | 49 ++++++++++++------ configure.in | 19 ++++++- plugins/sudoers/Makefile.in | 5 +- plugins/sudoers/audit.c | 11 ++++- plugins/sudoers/bsm_audit.c | 2 + plugins/sudoers/linux_audit.c | 93 +++++++++++++++++++++++++++++++++++ plugins/sudoers/linux_audit.h | 22 +++++++++ plugins/sudoers/logging.h | 4 +- src/selinux.c | 50 +++++++++++++++---- 13 files changed, 237 insertions(+), 32 deletions(-) create mode 100644 plugins/sudoers/linux_audit.c create mode 100644 plugins/sudoers/linux_audit.h diff --git a/INSTALL b/INSTALL index d90c84e88..4562f9828 100644 --- a/INSTALL +++ b/INSTALL @@ -144,6 +144,10 @@ Special features/options: --with-csops Add CSOps standard options. You probably aren't interested in this. + --with-linux-audit + Enable audit support for Linux systems. Audits attempts + to run a command as well as SELinux role changes. + --with-skey[=DIR] Enable S/Key OTP (One Time Password) support. If specified, DIR should contain include and lib directories with skey.h diff --git a/MANIFEST b/MANIFEST index 902135c86..34c957a1e 100644 --- a/MANIFEST +++ b/MANIFEST @@ -149,6 +149,8 @@ plugins/sudoers/interfaces.c plugins/sudoers/interfaces.h plugins/sudoers/iolog.c plugins/sudoers/ldap.c +plugins/sudoers/linux_audit.c +plugins/sudoers/linux_audit.h plugins/sudoers/logging.c plugins/sudoers/logging.h plugins/sudoers/match.c diff --git a/WHATSNEW b/WHATSNEW index 70c24489b..76a36edaa 100644 --- a/WHATSNEW +++ b/WHATSNEW @@ -30,6 +30,9 @@ What's new in Sudo 1.7.3? commands as a non-root user and when one of stdin, stdout or stderr is not a terminal. + * Sudo will now use the Linux audit system with configure with + the --with-linux-audit flag. + * Sudo now uses mbr_check_membership() on systems that support it to determine group membership. Currently, only Darwin (Mac OS X) supports this. diff --git a/config.h.in b/config.h.in index 4b89d48a0..ae7a45fb5 100644 --- a/config.h.in +++ b/config.h.in @@ -64,7 +64,7 @@ /* Define to 1 if you use BSD authentication. */ #undef HAVE_BSD_AUTH_H -/* Define to 1 to enable BSM auditing. */ +/* Define to 1 to enable BSM audit support. */ #undef HAVE_BSM_AUDIT /* Define to 1 if you have the `closefrom' function. */ @@ -301,6 +301,9 @@ /* Define to 1 if you have the `ldap_unbind_ext_s' function. */ #undef HAVE_LDAP_UNBIND_EXT_S +/* Define to 1 to enable Linux audit support. */ +#undef HAVE_LINUX_AUDIT + /* Define to 1 if you have the `lockf' function. */ #undef HAVE_LOCKF diff --git a/configure b/configure index 11b69b818..d9af86518 100755 --- a/configure +++ b/configure @@ -908,6 +908,7 @@ with_CC with_rpath with_blibpath with_bsm_audit +with_linux_audit with_incpath with_libpath with_libraries @@ -1663,6 +1664,7 @@ Optional Packages: --with-rpath pass -R flag in addition to -L for lib paths --with-blibpath=PATH pass -blibpath flag to ld for additional lib paths --with-bsm-audit enable BSM audit support + --with-linux-audit enable Linux audit support --with-incpath additional places to look for include files --with-libpath additional places to look for libraries --with-libraries additional libraries to link with @@ -2925,7 +2927,7 @@ if test "${with_bsm_audit+set}" = set; then : yes) $as_echo "#define HAVE_BSM_AUDIT 1" >>confdefs.h SUDOERS_LIBS="${SUDOERS_LIBS} -lbsm" - SUDOERS_OBJS="${SUDOERS_OBJS} bsm_audit.o" + SUDOERS_OBJS="${SUDOERS_OBJS} bsm_audit.lo" ;; no) ;; *) as_fn_error "\"--with-bsm-audit does not take an argument.\"" "$LINENO" 5 @@ -2935,6 +2937,22 @@ fi +# Check whether --with-linux-audit was given. +if test "${with_linux_audit+set}" = set; then : + withval=$with_linux_audit; case $with_linux_audit in + yes) $as_echo "#define HAVE_LINUX_AUDIT 1" >>confdefs.h + + SUDO_LIBS="${SUDOERS_LIBS} -laudit" + SUDOERS_OBJS="${SUDOERS_OBJS} linux_audit.lo" + ;; + no) ;; + *) as_fn_error "\"--with-linux-audit does not take an argument.\"" "$LINENO" 5 + ;; +esac +fi + + + # Check whether --with-incpath was given. if test "${with_incpath+set}" = set; then : withval=$with_incpath; case $with_incpath in @@ -6608,13 +6626,13 @@ if test "${lt_cv_nm_interface+set}" = set; then : else lt_cv_nm_interface="BSD nm" echo "int some_variable = 0;" > conftest.$ac_ext - (eval echo "\"\$as_me:6611: $ac_compile\"" >&5) + (eval echo "\"\$as_me:6629: $ac_compile\"" >&5) (eval "$ac_compile" 2>conftest.err) cat conftest.err >&5 - (eval echo "\"\$as_me:6614: $NM \\\"conftest.$ac_objext\\\"\"" >&5) + (eval echo "\"\$as_me:6632: $NM \\\"conftest.$ac_objext\\\"\"" >&5) (eval "$NM \"conftest.$ac_objext\"" 2>conftest.err > conftest.out) cat conftest.err >&5 - (eval echo "\"\$as_me:6617: output\"" >&5) + (eval echo "\"\$as_me:6635: output\"" >&5) cat conftest.out >&5 if $GREP 'External.*some_variable' conftest.out > /dev/null; then lt_cv_nm_interface="MS dumpbin" @@ -7819,7 +7837,7 @@ ia64-*-hpux*) ;; *-*-irix6*) # Find out which ABI we are using. - echo '#line 7822 "configure"' > conftest.$ac_ext + echo '#line 7840 "configure"' > conftest.$ac_ext if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 (eval $ac_compile) 2>&5 ac_status=$? @@ -9211,11 +9229,11 @@ else -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ -e 's:$: $lt_compiler_flag:'` - (eval echo "\"\$as_me:9214: $lt_compile\"" >&5) + (eval echo "\"\$as_me:9232: $lt_compile\"" >&5) (eval "$lt_compile" 2>conftest.err) ac_status=$? cat conftest.err >&5 - echo "$as_me:9218: \$? = $ac_status" >&5 + echo "$as_me:9236: \$? = $ac_status" >&5 if (exit $ac_status) && test -s "$ac_outfile"; then # The compiler can only warn and ignore the option if not recognized # So say no if there are warnings other than the usual output. @@ -9550,11 +9568,11 @@ else -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ -e 's:$: $lt_compiler_flag:'` - (eval echo "\"\$as_me:9553: $lt_compile\"" >&5) + (eval echo "\"\$as_me:9571: $lt_compile\"" >&5) (eval "$lt_compile" 2>conftest.err) ac_status=$? cat conftest.err >&5 - echo "$as_me:9557: \$? = $ac_status" >&5 + echo "$as_me:9575: \$? = $ac_status" >&5 if (exit $ac_status) && test -s "$ac_outfile"; then # The compiler can only warn and ignore the option if not recognized # So say no if there are warnings other than the usual output. @@ -9655,11 +9673,11 @@ else -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ -e 's:$: $lt_compiler_flag:'` - (eval echo "\"\$as_me:9658: $lt_compile\"" >&5) + (eval echo "\"\$as_me:9676: $lt_compile\"" >&5) (eval "$lt_compile" 2>out/conftest.err) ac_status=$? cat out/conftest.err >&5 - echo "$as_me:9662: \$? = $ac_status" >&5 + echo "$as_me:9680: \$? = $ac_status" >&5 if (exit $ac_status) && test -s out/conftest2.$ac_objext then # The compiler can only warn and ignore the option if not recognized @@ -9710,11 +9728,11 @@ else -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ -e 's:$: $lt_compiler_flag:'` - (eval echo "\"\$as_me:9713: $lt_compile\"" >&5) + (eval echo "\"\$as_me:9731: $lt_compile\"" >&5) (eval "$lt_compile" 2>out/conftest.err) ac_status=$? cat out/conftest.err >&5 - echo "$as_me:9717: \$? = $ac_status" >&5 + echo "$as_me:9735: \$? = $ac_status" >&5 if (exit $ac_status) && test -s out/conftest2.$ac_objext then # The compiler can only warn and ignore the option if not recognized @@ -12077,7 +12095,7 @@ else lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2 lt_status=$lt_dlunknown cat > conftest.$ac_ext <<_LT_EOF -#line 12080 "configure" +#line 12098 "configure" #include "confdefs.h" #if HAVE_DLFCN_H @@ -12173,7 +12191,7 @@ else lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2 lt_status=$lt_dlunknown cat > conftest.$ac_ext <<_LT_EOF -#line 12176 "configure" +#line 12194 "configure" #include "confdefs.h" #if HAVE_DLFCN_H @@ -20343,5 +20361,6 @@ fi + diff --git a/configure.in b/configure.in index e02e474b9..cf2e85805 100644 --- a/configure.in +++ b/configure.in @@ -235,13 +235,27 @@ AC_ARG_WITH(bsm-audit, [AS_HELP_STRING([--with-bsm-audit], [enable BSM audit sup [case $with_bsm_audit in yes) AC_DEFINE(HAVE_BSM_AUDIT) SUDOERS_LIBS="${SUDOERS_LIBS} -lbsm" - SUDOERS_OBJS="${SUDOERS_OBJS} bsm_audit.o" + SUDOERS_OBJS="${SUDOERS_OBJS} bsm_audit.lo" ;; no) ;; *) AC_MSG_ERROR(["--with-bsm-audit does not take an argument."]) ;; esac]) +dnl +dnl Handle Linux auditing support. +dnl +AC_ARG_WITH(linux-audit, [AS_HELP_STRING([--with-linux-audit], [enable Linux audit support])], +[case $with_linux_audit in + yes) AC_DEFINE(HAVE_LINUX_AUDIT) + SUDO_LIBS="${SUDOERS_LIBS} -laudit" + SUDOERS_OBJS="${SUDOERS_OBJS} linux_audit.lo" + ;; + no) ;; + *) AC_MSG_ERROR(["--with-linux-audit does not take an argument."]) + ;; +esac]) + AC_ARG_WITH(incpath, [AS_HELP_STRING([--with-incpath], [additional places to look for include files])], [case $with_incpath in yes) AC_MSG_ERROR(["must give --with-incpath an argument."]) @@ -2786,7 +2800,7 @@ AH_TEMPLATE(HAL_INSULTS, [Define to 1 if you want 2001-like insults.]) AH_TEMPLATE(HAVE_AFS, [Define to 1 if you use AFS.]) AH_TEMPLATE(HAVE_AIXAUTH, [Define to 1 if you use AIX general authentication.]) AH_TEMPLATE(HAVE_BSD_AUTH_H, [Define to 1 if you use BSD authentication.]) -AH_TEMPLATE(HAVE_BSM_AUDIT, [Define to 1 to enable BSM auditing.]) +AH_TEMPLATE(HAVE_BSM_AUDIT, [Define to 1 to enable BSM audit support.]) AH_TEMPLATE(HAVE_DCE, [Define to 1 if you use OSF DCE.]) AH_TEMPLATE(HAVE_DD_FD, [Define to 1 if your `DIR' contains dd_fd.]) AH_TEMPLATE(HAVE_DIRFD, [Define to 1 if you have the `dirfd' function or macro.]) @@ -2814,6 +2828,7 @@ AH_TEMPLATE(HAVE_KRB5_INIT_SECURE_CONTEXT, [Define to 1 if you have the `krb5_in AH_TEMPLATE(HAVE_KRB5_VERIFY_USER, [Define to 1 if you have the `krb5_verify_user' function.]) AH_TEMPLATE(HAVE_LBER_H, [Define to 1 if your LDAP needs . (OpenLDAP does not)]) AH_TEMPLATE(HAVE_LDAP, [Define to 1 if you use LDAP for sudoers.]) +AH_TEMPLATE(HAVE_LINUX_AUDIT, [Define to 1 to enable Linux audit support.]) AH_TEMPLATE(HAVE_OPIE, [Define to 1 if you use NRL OPIE.]) AH_TEMPLATE(HAVE_PAM, [Define to 1 if you use PAM authentication.]) AH_TEMPLATE(HAVE_PROJECT_H, [Define to 1 if you have the header file.]) diff --git a/plugins/sudoers/Makefile.in b/plugins/sudoers/Makefile.in index 4f0dd87e2..c599b2884 100644 --- a/plugins/sudoers/Makefile.in +++ b/plugins/sudoers/Makefile.in @@ -186,8 +186,8 @@ $(devdir)/getdate.c: $(srcdir)/getdate.y # Sudoers dependencies alias.lo: $(srcdir)/alias.c $(SUDODEP) $(srcdir)/parse.h $(incdir)/list.h $(srcdir)/redblack.h -audit.lo: audit.c $(SUDODEP) -bsm_audit.lo: bsm_audit.c $(SUDODEP) $(srcdir)/bsm_audit.h +audit.lo: $(srcdir)/audit.c $(SUDODEP) +bsm_audit.lo: $(srcdir)/bsm_audit.c $(SUDODEP) $(srcdir)/bsm_audit.h boottime.lo: $(srcdir)/boottime.c $(top_builddir)/config.h check.lo: $(srcdir)/check.c $(SUDODEP) defaults.lo: $(srcdir)/defaults.c $(SUDODEP) $(devdir)/def_data.c $(authdir)/sudo_auth.h $(devdir)/gram.h @@ -200,6 +200,7 @@ gram.lo: $(devdir)/gram.c $(SUDODEP) $(srcdir)/parse.h $(incdir)/list.h $(devdir interfaces.lo: $(srcdir)/interfaces.c $(SUDODEP) $(srcdir)/interfaces.h iolog.lo: $(srcdir)/iolog.c $(SUDODEP) ldap.lo: $(srcdir)/ldap.c $(SUDODEP) $(srcdir)/parse.h $(incdir)/list.h +linux_audit.lo: $(srcdir)/linux_audit.c $(SUDODEP) $(srcdir)/linux_audit.h logging.lo: $(srcdir)/logging.c $(SUDODEP) match.lo: $(srcdir)/match.c $(SUDODEP) $(srcdir)/parse.h $(incdir)/list.h $(srcdir)/interfaces.h $(devdir)/gram.h parse.lo: $(srcdir)/parse.c $(SUDODEP) $(srcdir)/parse.h $(incdir)/list.h $(devdir)/gram.h diff --git a/plugins/sudoers/audit.c b/plugins/sudoers/audit.c index 47b03e414..b05a02ac0 100644 --- a/plugins/sudoers/audit.c +++ b/plugins/sudoers/audit.c @@ -34,13 +34,19 @@ #ifdef HAVE_BSM_AUDIT # include "bsm_audit.h" #endif +#ifdef HAVE_LINUX_AUDIT +# include "linux_audit.h" +#endif void -audit_success(char **exec_args) +audit_success(char *exec_args[]) { #ifdef HAVE_BSM_AUDIT bsm_audit_success(exec_args); #endif +#ifdef HAVE_LINUX_AUDIT + linux_audit_command(exec_args, 1); +#endif } void @@ -51,6 +57,9 @@ audit_failure(char **exec_args, char const *const fmt, ...) va_start(ap, fmt); #ifdef HAVE_BSM_AUDIT bsm_audit_failure(exec_args, fmt, ap); +#endif +#ifdef HAVE_LINUX_AUDIT + linux_audit_command(exec_args, 0); #endif va_end(ap); } diff --git a/plugins/sudoers/bsm_audit.c b/plugins/sudoers/bsm_audit.c index f856f574f..2b16f0510 100644 --- a/plugins/sudoers/bsm_audit.c +++ b/plugins/sudoers/bsm_audit.c @@ -30,6 +30,8 @@ #include #include +#include "bsm_audit.h" + void log_error(int flags, const char *fmt, ...) __attribute__((__noreturn__)); static int diff --git a/plugins/sudoers/linux_audit.c b/plugins/sudoers/linux_audit.c new file mode 100644 index 000000000..b18a3c2e3 --- /dev/null +++ b/plugins/sudoers/linux_audit.c @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2010 Todd C. Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include +#include +#ifdef STDC_HEADERS +# include +# include +#else +# ifdef HAVE_STDLIB_H +# include +# endif +#endif /* STDC_HEADERS */ +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +/* + * Open audit connection if possible. + * Returns audit fd on success and -1 on failure. + */ +static int +linux_audit_open(void) +{ + static int au_fd = -1; + + if (au_fd != -1) + return au_fd; + au_fd = audit_open(); + if (au_fd == -1) { + /* Kernel may not have audit support. */ + if (errno != EINVAL && errno != EPROTONOSUPPORT && errno != EAFNOSUPPORT) + error(1, "unable to open audit system"); + } else { + (void)fcntl(au_fd, F_SETFD, FD_CLOEXEC); + } + return au_fd; +} + +int +linux_audit_command(char *argv[], int result) +{ + int au_fd, rc; + char *command, *cp, **av; + size_t size, n; + + if ((au_fd = linux_audit_open()) == -1) + return -1; + + /* Convert argv to a flat string. */ + for (size = 0, av = argv; *av != NULL; av++) + size += strlen(*av) + 1; + command = cp = emalloc(size); + for (av = argv; *av != NULL; av++) { + n = strlcpy(cp, *av, size - (cp - command)); + if (n >= size - (cp - command)) + errorx(1, "internal error, linux_audit_command() overflow"); + cp += n; + *cp++ = ' '; + } + *--cp = '\0'; + + /* Log command, ignoring EPERM on error. */ + rc = audit_log_user_command(au_fd, AUDIT_USER_CMD, command, NULL, result); + if (rc <= 0) + warning("unable to send audit message"); + + efree(command); + + return rc; +} diff --git a/plugins/sudoers/linux_audit.h b/plugins/sudoers/linux_audit.h new file mode 100644 index 000000000..8f4d46c7f --- /dev/null +++ b/plugins/sudoers/linux_audit.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2010 Todd C. Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _SUDO_LINUX_AUDIT_H +#define _SUDO_LINUX_AUDIT_H + +int linux_audit_command(char *argv[], int result); + +#endif /* _SUDO_LINUX_AUDIT_H */ diff --git a/plugins/sudoers/logging.h b/plugins/sudoers/logging.h index 591da3259..43d5ff096 100644 --- a/plugins/sudoers/logging.h +++ b/plugins/sudoers/logging.h @@ -47,8 +47,8 @@ # define MAXSYSLOGLEN 960 #endif -void audit_success(char **); -void audit_failure(char **, char const * const, ...); +void audit_success(char *[]); +void audit_failure(char *[], char const * const, ...); void log_allowed(int); void log_denial(int, int); void log_error(int flags, const char *fmt, ...) __printflike(2, 3); diff --git a/src/selinux.c b/src/selinux.c index c74e9cd02..c577753c8 100644 --- a/src/selinux.c +++ b/src/selinux.c @@ -36,9 +36,6 @@ #include #include #include -#ifdef WITH_AUDIT -#include -#endif #include /* for SECCLASS_CHR_FILE */ #include /* for is_selinux_enabled() */ @@ -46,8 +43,11 @@ #include #include +#ifdef HAVE_LINUX_AUDIT +# include +#endif + #include "sudo.h" -#include "pathnames.h" static struct selinux_state { security_context_t old_context; @@ -59,6 +59,38 @@ static struct selinux_state { int enforcing; } se_state; +#ifdef HAVE_LINUX_AUDIT +static int +audit_role_change(const security_context_t old_context, + const security_context_t new_context, const char *ttyn) +{ + int au_fd, rc; + char *message; + + au_fd = audit_open(); + if (au_fd == -1) { + /* Kernel may not have audit support. */ + if (errno != EINVAL && errno != EPROTONOSUPPORT && errno != EAFNOSUPPORT +) + error(1, "unable to open audit system"); + return -1; + } + + /* audit role change using the same format as newrole(1) */ + easprintf(&message, "newrole: old-context=%s new-context=%s", + old_context, new_context); + rc = audit_log_user_message(au_fd, AUDIT_USER_ROLE_CHANGE, + message, NULL, NULL, ttyn, 1); + if (rc <= 0) + warning("unable to send audit message"); + + efree(message); + close(au_fd); + + return rc; +} +#endif + /* * This function attempts to revert the relabeling done to the tty. * fd - referencing the opened ttyn @@ -314,6 +346,11 @@ selinux_setup(const char *role, const char *type, const char *ttyn, } #endif +#ifdef HAVE_LINUX_AUDIT + audit_role_change(se_state.old_context, se_state.new_context, + se_state.ttyn); +#endif + rval = 0; done: @@ -338,11 +375,6 @@ selinux_execve(const char *path, char *argv[], char *envp[]) return; } -#ifdef WITH_AUDIT - if (send_audit_message(1, se_state.old_context, se_state.new_context, se_state.ttyn)) - return; -#endif - for (argc = 0; argv[argc] != NULL; argc++) continue;