562 lines
18 KiB
Diff
562 lines
18 KiB
Diff
Index: modules/pam_selinux/pam_selinux.8.xml
|
|
===================================================================
|
|
RCS file: /cvsroot/pam/Linux-PAM/modules/pam_selinux/pam_selinux.8.xml,v
|
|
retrieving revision 1.2
|
|
diff -u -p -r1.2 pam_selinux.8.xml
|
|
--- modules/pam_selinux/pam_selinux.8.xml 15 Jun 2007 10:17:22 -0000 1.2
|
|
+++ modules/pam_selinux/pam_selinux.8.xml 19 May 2008 15:44:08 -0000
|
|
@@ -37,6 +37,9 @@
|
|
select_context
|
|
</arg>
|
|
<arg choice="opt">
|
|
+ env_params
|
|
+ </arg>
|
|
+ <arg choice="opt">
|
|
use_current_range
|
|
</arg>
|
|
</cmdsynopsis>
|
|
@@ -137,12 +140,30 @@
|
|
</varlistentry>
|
|
<varlistentry>
|
|
<term>
|
|
+ <option>env_params</option>
|
|
+ </term>
|
|
+ <listitem>
|
|
+ <para>
|
|
+ Attempt to obtain a custom security context role from PAM environment.
|
|
+ If MLS is on obtain also sensitivity level. This option and the
|
|
+ select_context option are mutually exclusive. The respective PAM
|
|
+ environment variables are <emphasis>SELINUX_ROLE_REQUESTED</emphasis>,
|
|
+ <emphasis>SELINUX_LEVEL_REQUESTED</emphasis>, and
|
|
+ <emphasis>SELINUX_USE_CURRENT_RANGE</emphasis>. The first two variables
|
|
+ are self describing and the last one if set to 1 makes the PAM module behave as
|
|
+ if the use_current_range was specified on the command line of the module.
|
|
+ </para>
|
|
+ </listitem>
|
|
+ </varlistentry>
|
|
+ <varlistentry>
|
|
+ <term>
|
|
<option>use_current_range</option>
|
|
</term>
|
|
<listitem>
|
|
<para>
|
|
- Use the sensitivity range of the process for the user context.
|
|
- This option and the select_context option are mutually exclusive.
|
|
+ Use the sensitivity level of the current process for the user context
|
|
+ instead of the default level. Also supresses asking of the
|
|
+ sensitivity level from the user or obtaining it from PAM environment.
|
|
</para>
|
|
</listitem>
|
|
</varlistentry>
|
|
Index: modules/pam_selinux/pam_selinux.c
|
|
===================================================================
|
|
RCS file: /cvsroot/pam/Linux-PAM/modules/pam_selinux/pam_selinux.c,v
|
|
retrieving revision 1.16
|
|
diff -u -p -r1.16 pam_selinux.c
|
|
--- modules/pam_selinux/pam_selinux.c 22 Apr 2008 19:21:37 -0000 1.16
|
|
+++ modules/pam_selinux/pam_selinux.c 19 May 2008 15:44:08 -0000
|
|
@@ -2,8 +2,9 @@
|
|
* A module for Linux-PAM that will set the default security context after login
|
|
* via PAM.
|
|
*
|
|
- * Copyright (c) 2003 Red Hat, Inc.
|
|
+ * Copyright (c) 2003-2008 Red Hat, Inc.
|
|
* Written by Dan Walsh <dwalsh@redhat.com>
|
|
+ * Additional improvements by Tomas Mraz <tmraz@redhat.com>
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
@@ -138,15 +139,22 @@ send_text (pam_handle_t *pamh, const cha
|
|
*/
|
|
static int
|
|
query_response (pam_handle_t *pamh, const char *text, const char *def,
|
|
- char **responses, int debug)
|
|
+ char **response, int debug)
|
|
{
|
|
int rc;
|
|
if (def)
|
|
- rc = pam_prompt (pamh, PAM_PROMPT_ECHO_ON, responses, "%s [%s] ", text, def);
|
|
+ rc = pam_prompt (pamh, PAM_PROMPT_ECHO_ON, response, "%s [%s] ", text, def);
|
|
else
|
|
- rc = pam_prompt (pamh, PAM_PROMPT_ECHO_ON, responses, "%s ", text);
|
|
- if (debug)
|
|
- pam_syslog(pamh, LOG_NOTICE, "%s %s", text, responses[0]);
|
|
+ rc = pam_prompt (pamh, PAM_PROMPT_ECHO_ON, response, "%s ", text);
|
|
+
|
|
+ if (*response == NULL) {
|
|
+ rc = PAM_CONV_ERR;
|
|
+ }
|
|
+
|
|
+ if (rc != PAM_SUCCESS) {
|
|
+ pam_syslog(pamh, LOG_WARNING, "No response to query: %s", text);
|
|
+ } else if (debug)
|
|
+ pam_syslog(pamh, LOG_NOTICE, "%s %s", text, *response);
|
|
return rc;
|
|
}
|
|
|
|
@@ -157,13 +165,15 @@ manual_context (pam_handle_t *pamh, cons
|
|
context_t new_context;
|
|
int mls_enabled = is_selinux_mls_enabled();
|
|
char *type=NULL;
|
|
- char *responses=NULL;
|
|
+ char *response=NULL;
|
|
|
|
while (1) {
|
|
- query_response(pamh,
|
|
- _("Would you like to enter a security context? [N] "), NULL,
|
|
- &responses,debug);
|
|
- if ((responses[0] == 'y') || (responses[0] == 'Y'))
|
|
+ if (query_response(pamh,
|
|
+ _("Would you like to enter a security context? [N] "), NULL,
|
|
+ &response, debug) != PAM_SUCCESS)
|
|
+ return NULL;
|
|
+
|
|
+ if ((response[0] == 'y') || (response[0] == 'Y'))
|
|
{
|
|
if (mls_enabled)
|
|
new_context = context_new ("user:role:type:level");
|
|
@@ -176,26 +186,29 @@ manual_context (pam_handle_t *pamh, cons
|
|
if (context_user_set (new_context, user))
|
|
goto fail_set;
|
|
|
|
- _pam_drop(responses);
|
|
+ _pam_drop(response);
|
|
/* Allow the user to enter each field of the context individually */
|
|
- query_response(pamh,_("role:"), NULL, &responses,debug);
|
|
- if (responses[0] != '\0') {
|
|
- if (context_role_set (new_context, responses))
|
|
+ if (query_response(pamh, _("role:"), NULL, &response, debug) == PAM_SUCCESS &&
|
|
+ response[0] != '\0') {
|
|
+ if (context_role_set (new_context, response))
|
|
goto fail_set;
|
|
- if (get_default_type(responses, &type))
|
|
+ if (get_default_type(response, &type))
|
|
goto fail_set;
|
|
if (context_type_set (new_context, type))
|
|
goto fail_set;
|
|
}
|
|
- _pam_drop(responses);
|
|
+ _pam_drop(response);
|
|
+
|
|
if (mls_enabled)
|
|
{
|
|
- query_response(pamh,_("level:"), NULL, &responses,debug);
|
|
- if (responses[0] != '\0') {
|
|
- if (context_range_set (new_context, responses))
|
|
+ if (query_response(pamh, _("level:"), NULL, &response, debug) == PAM_SUCCESS &&
|
|
+ response[0] != '\0') {
|
|
+ if (context_range_set (new_context, response))
|
|
goto fail_set;
|
|
}
|
|
+ _pam_drop(response);
|
|
}
|
|
+
|
|
/* Get the string value of the context and see if it is valid. */
|
|
if (!security_check_context(context_str(new_context))) {
|
|
newcon = strdup(context_str(new_context));
|
|
@@ -204,16 +217,17 @@ manual_context (pam_handle_t *pamh, cons
|
|
}
|
|
else
|
|
send_text(pamh,_("Not a valid security context"),debug);
|
|
- context_free (new_context);
|
|
+
|
|
+ context_free (new_context);
|
|
}
|
|
else {
|
|
- _pam_drop(responses);
|
|
+ _pam_drop(response);
|
|
return NULL;
|
|
}
|
|
} /* end while */
|
|
fail_set:
|
|
free(type);
|
|
- _pam_drop(responses);
|
|
+ _pam_drop(response);
|
|
context_free (new_context);
|
|
return NULL;
|
|
}
|
|
@@ -239,69 +253,91 @@ static int mls_range_allowed(pam_handle_
|
|
}
|
|
|
|
static security_context_t
|
|
-config_context (pam_handle_t *pamh, security_context_t puser_context, int debug)
|
|
+config_context (pam_handle_t *pamh, security_context_t defaultcon, int use_current_range, int debug)
|
|
{
|
|
security_context_t newcon=NULL;
|
|
context_t new_context;
|
|
int mls_enabled = is_selinux_mls_enabled();
|
|
- char *responses=NULL;
|
|
+ char *response=NULL;
|
|
char *type=NULL;
|
|
char resp_val = 0;
|
|
|
|
- pam_prompt (pamh, PAM_TEXT_INFO, NULL, _("Default Security Context %s\n"), puser_context);
|
|
+ pam_prompt (pamh, PAM_TEXT_INFO, NULL, _("Default Security Context %s\n"), defaultcon);
|
|
|
|
while (1) {
|
|
- query_response(pamh,
|
|
+ if (query_response(pamh,
|
|
_("Would you like to enter a different role or level?"), "n",
|
|
- &responses,debug);
|
|
-
|
|
- resp_val = responses[0];
|
|
- _pam_drop(responses);
|
|
+ &response, debug) == PAM_SUCCESS) {
|
|
+ resp_val = response[0];
|
|
+ _pam_drop(response);
|
|
+ } else {
|
|
+ resp_val = 'N';
|
|
+ }
|
|
if ((resp_val == 'y') || (resp_val == 'Y'))
|
|
{
|
|
- new_context = context_new(puser_context);
|
|
-
|
|
+ if ((new_context = context_new(defaultcon)) == NULL)
|
|
+ goto fail_set;
|
|
+
|
|
/* Allow the user to enter role and level individually */
|
|
- query_response(pamh,_("role:"), context_role_get(new_context),
|
|
- &responses, debug);
|
|
- if (responses[0]) {
|
|
- if (get_default_type(responses, &type)) {
|
|
- pam_prompt (pamh, PAM_ERROR_MSG, NULL, _("No default type for role %s\n"), responses);
|
|
- _pam_drop(responses);
|
|
+ if (query_response(pamh, _("role:"), context_role_get(new_context),
|
|
+ &response, debug) == PAM_SUCCESS && response[0]) {
|
|
+ if (get_default_type(response, &type)) {
|
|
+ pam_prompt (pamh, PAM_ERROR_MSG, NULL, _("No default type for role %s\n"), response);
|
|
+ _pam_drop(response);
|
|
continue;
|
|
} else {
|
|
- if (context_role_set(new_context, responses))
|
|
+ if (context_role_set(new_context, response))
|
|
goto fail_set;
|
|
if (context_type_set (new_context, type))
|
|
goto fail_set;
|
|
}
|
|
}
|
|
- _pam_drop(responses);
|
|
+ _pam_drop(response);
|
|
+
|
|
if (mls_enabled)
|
|
{
|
|
- query_response(pamh,_("level:"), context_range_get(new_context),
|
|
- &responses, debug);
|
|
- if (responses[0]) {
|
|
- if (context_range_set(new_context, responses))
|
|
- goto fail_set;
|
|
+ if (use_current_range) {
|
|
+ security_context_t mycon = NULL;
|
|
+ context_t my_context;
|
|
+
|
|
+ if (getcon(&mycon) != 0)
|
|
+ goto fail_set;
|
|
+ my_context = context_new(mycon);
|
|
+ if (my_context == NULL) {
|
|
+ freecon(mycon);
|
|
+ goto fail_set;
|
|
+ }
|
|
+ freecon(mycon);
|
|
+ if (context_range_set(new_context, context_range_get(my_context))) {
|
|
+ context_free(my_context);
|
|
+ goto fail_set;
|
|
+ }
|
|
+ context_free(my_context);
|
|
+ } else if (query_response(pamh, _("level:"), context_range_get(new_context),
|
|
+ &response, debug) == PAM_SUCCESS && response[0]) {
|
|
+ if (context_range_set(new_context, response))
|
|
+ goto fail_set;
|
|
}
|
|
- _pam_drop(responses);
|
|
+ _pam_drop(response);
|
|
}
|
|
+
|
|
if (debug)
|
|
pam_syslog(pamh, LOG_NOTICE, "Selected Security Context %s", context_str(new_context));
|
|
|
|
/* Get the string value of the context and see if it is valid. */
|
|
if (!security_check_context(context_str(new_context))) {
|
|
newcon = strdup(context_str(new_context));
|
|
- context_free (new_context);
|
|
+ if (newcon == NULL)
|
|
+ goto fail_set;
|
|
+ context_free(new_context);
|
|
|
|
/* we have to check that this user is allowed to go into the
|
|
range they have specified ... role is tied to an seuser, so that'll
|
|
be checked at setexeccon time */
|
|
- if (mls_enabled && !mls_range_allowed(pamh, puser_context, newcon, debug)) {
|
|
- pam_syslog(pamh, LOG_NOTICE, "Security context %s is not allowed for %s", puser_context, newcon);
|
|
+ if (mls_enabled && !mls_range_allowed(pamh, defaultcon, newcon, debug)) {
|
|
+ pam_syslog(pamh, LOG_NOTICE, "Security context %s is not allowed for %s", defaultcon, newcon);
|
|
|
|
- send_audit_message(pamh, 0, puser_context, newcon);
|
|
+ send_audit_message(pamh, 0, defaultcon, newcon);
|
|
|
|
free(newcon);
|
|
goto fail_range;
|
|
@@ -309,26 +345,120 @@ config_context (pam_handle_t *pamh, secu
|
|
return newcon;
|
|
}
|
|
else {
|
|
- send_audit_message(pamh, 0, puser_context, context_str(new_context));
|
|
+ send_audit_message(pamh, 0, defaultcon, context_str(new_context));
|
|
send_text(pamh,_("Not a valid security context"),debug);
|
|
}
|
|
context_free(new_context); /* next time around allocates another */
|
|
}
|
|
else
|
|
- return strdup(puser_context);
|
|
+ return strdup(defaultcon);
|
|
} /* end while */
|
|
|
|
return NULL;
|
|
|
|
fail_set:
|
|
free(type);
|
|
- _pam_drop(responses);
|
|
+ _pam_drop(response);
|
|
context_free (new_context);
|
|
- send_audit_message(pamh, 0, puser_context, NULL);
|
|
+ send_audit_message(pamh, 0, defaultcon, NULL);
|
|
fail_range:
|
|
return NULL;
|
|
}
|
|
|
|
+static security_context_t
|
|
+context_from_env (pam_handle_t *pamh, security_context_t defaultcon, int env_params, int use_current_range, int debug)
|
|
+{
|
|
+ security_context_t newcon = NULL;
|
|
+ context_t new_context;
|
|
+ context_t my_context = NULL;
|
|
+ int mls_enabled = is_selinux_mls_enabled();
|
|
+ const char *env = NULL;
|
|
+ char *type = NULL;
|
|
+
|
|
+ if ((new_context = context_new(defaultcon)) == NULL)
|
|
+ goto fail_set;
|
|
+
|
|
+ if (env_params && (env = pam_getenv(pamh, "SELINUX_ROLE_REQUESTED")) != NULL && env[0] != '\0') {
|
|
+ if (debug)
|
|
+ pam_syslog(pamh, LOG_NOTICE, "Requested role: %s", env);
|
|
+
|
|
+ if (get_default_type(env, &type)) {
|
|
+ pam_syslog(pamh, LOG_NOTICE, "No default type for role %s", env);
|
|
+ goto fail_set;
|
|
+ } else {
|
|
+ if (context_role_set(new_context, env))
|
|
+ goto fail_set;
|
|
+ if (context_type_set(new_context, type))
|
|
+ goto fail_set;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (mls_enabled) {
|
|
+ if ((env = pam_getenv(pamh, "SELINUX_USE_CURRENT_RANGE")) != NULL && env[0] == '1') {
|
|
+ if (debug)
|
|
+ pam_syslog(pamh, LOG_NOTICE, "SELINUX_USE_CURRENT_RANGE is set");
|
|
+ use_current_range = 1;
|
|
+ }
|
|
+
|
|
+ if (use_current_range) {
|
|
+ security_context_t mycon = NULL;
|
|
+
|
|
+ if (getcon(&mycon) != 0)
|
|
+ goto fail_set;
|
|
+ my_context = context_new(mycon);
|
|
+ if (my_context == NULL) {
|
|
+ freecon(mycon);
|
|
+ goto fail_set;
|
|
+ }
|
|
+ freecon(mycon);
|
|
+ env = context_range_get(my_context);
|
|
+ } else {
|
|
+ env = pam_getenv(pamh, "SELINUX_LEVEL_REQUESTED");
|
|
+ }
|
|
+
|
|
+ if (env != NULL && env[0] != '\0') {
|
|
+ if (debug)
|
|
+ pam_syslog(pamh, LOG_NOTICE, "Requested level: %s", env);
|
|
+ if (context_range_set(new_context, env))
|
|
+ goto fail_set;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ newcon = strdup(context_str(new_context));
|
|
+ if (newcon == NULL)
|
|
+ goto fail_set;
|
|
+
|
|
+ if (debug)
|
|
+ pam_syslog(pamh, LOG_NOTICE, "Selected Security Context %s", newcon);
|
|
+
|
|
+ /* Get the string value of the context and see if it is valid. */
|
|
+ if (security_check_context(newcon)) {
|
|
+ pam_syslog(pamh, LOG_NOTICE, "Not a valid security context %s", newcon);
|
|
+ send_audit_message(pamh, 0, defaultcon, newcon);
|
|
+ freecon(newcon);
|
|
+ newcon = NULL;
|
|
+
|
|
+ goto fail_set;
|
|
+ }
|
|
+
|
|
+ /* we have to check that this user is allowed to go into the
|
|
+ range they have specified ... role is tied to an seuser, so that'll
|
|
+ be checked at setexeccon time */
|
|
+ if (mls_enabled && !mls_range_allowed(pamh, defaultcon, newcon, debug)) {
|
|
+ pam_syslog(pamh, LOG_NOTICE, "Security context %s is not allowed for %s", defaultcon, newcon);
|
|
+ send_audit_message(pamh, 0, defaultcon, newcon);
|
|
+ freecon(newcon);
|
|
+ newcon = NULL;
|
|
+ }
|
|
+
|
|
+ fail_set:
|
|
+ free(type);
|
|
+ context_free(my_context);
|
|
+ context_free(new_context);
|
|
+ send_audit_message(pamh, 0, defaultcon, NULL);
|
|
+ return newcon;
|
|
+}
|
|
+
|
|
static void
|
|
security_restorelabel_tty(const pam_handle_t *pamh,
|
|
const char *tty, security_context_t context)
|
|
@@ -439,13 +569,14 @@ PAM_EXTERN int
|
|
pam_sm_open_session(pam_handle_t *pamh, int flags UNUSED,
|
|
int argc, const char **argv)
|
|
{
|
|
- int i, debug = 0, ttys=1, has_tty=isatty(0);
|
|
+ int i, debug = 0, ttys=1;
|
|
int verbose=0, close_session=0;
|
|
int select_context = 0;
|
|
int use_current_range = 0;
|
|
int ret = 0;
|
|
security_context_t* contextlist = NULL;
|
|
int num_contexts = 0;
|
|
+ int env_params = 0;
|
|
const char *username = NULL;
|
|
const void *tty = NULL;
|
|
char *seuser=NULL;
|
|
@@ -472,13 +603,16 @@ pam_sm_open_session(pam_handle_t *pamh,
|
|
if (strcmp(argv[i], "use_current_range") == 0) {
|
|
use_current_range = 1;
|
|
}
|
|
+ if (strcmp(argv[i], "env_params") == 0) {
|
|
+ env_params = 1;
|
|
+ }
|
|
}
|
|
|
|
if (debug)
|
|
pam_syslog(pamh, LOG_NOTICE, "Open Session");
|
|
|
|
- if (select_context && use_current_range) {
|
|
- pam_syslog(pamh, LOG_ERR, "select_context cannot be used with use_current_range");
|
|
+ if (select_context && env_params) {
|
|
+ pam_syslog(pamh, LOG_ERR, "select_context cannot be used with env_params");
|
|
select_context = 0;
|
|
}
|
|
|
|
@@ -510,12 +644,17 @@ pam_sm_open_session(pam_handle_t *pamh,
|
|
freeconary(contextlist);
|
|
if (default_user_context == NULL) {
|
|
pam_syslog(pamh, LOG_ERR, "Out of memory");
|
|
- return PAM_AUTH_ERR;
|
|
+ return PAM_BUF_ERR;
|
|
}
|
|
+
|
|
user_context = default_user_context;
|
|
- if (select_context && has_tty) {
|
|
- user_context = config_context(pamh, default_user_context, debug);
|
|
- if (user_context == NULL) {
|
|
+ if (select_context) {
|
|
+ user_context = config_context(pamh, default_user_context, use_current_range, debug);
|
|
+ } else if (env_params || use_current_range) {
|
|
+ user_context = context_from_env(pamh, default_user_context, env_params, use_current_range, debug);
|
|
+ }
|
|
+
|
|
+ if (user_context == NULL) {
|
|
freecon(default_user_context);
|
|
pam_syslog(pamh, LOG_ERR, "Unable to get valid context for %s",
|
|
username);
|
|
@@ -524,11 +663,9 @@ pam_sm_open_session(pam_handle_t *pamh,
|
|
return PAM_AUTH_ERR;
|
|
else
|
|
return PAM_SUCCESS;
|
|
- }
|
|
- }
|
|
+ }
|
|
}
|
|
else {
|
|
- if (has_tty) {
|
|
user_context = manual_context(pamh,seuser,debug);
|
|
if (user_context == NULL) {
|
|
pam_syslog (pamh, LOG_ERR, "Unable to get valid context for %s",
|
|
@@ -538,59 +675,6 @@ pam_sm_open_session(pam_handle_t *pamh,
|
|
else
|
|
return PAM_SUCCESS;
|
|
}
|
|
- } else {
|
|
- pam_syslog (pamh, LOG_ERR,
|
|
- "Unable to get valid context for %s, No valid tty",
|
|
- username);
|
|
- if (security_getenforce() == 1)
|
|
- return PAM_AUTH_ERR;
|
|
- else
|
|
- return PAM_SUCCESS;
|
|
- }
|
|
- }
|
|
-
|
|
- if (use_current_range && is_selinux_mls_enabled()) {
|
|
- security_context_t process_context=NULL;
|
|
- if (getcon(&process_context) == 0) {
|
|
- context_t pcon, ucon;
|
|
- char *process_level=NULL;
|
|
- security_context_t orig_context;
|
|
-
|
|
- if (user_context)
|
|
- orig_context = user_context;
|
|
- else
|
|
- orig_context = default_user_context;
|
|
-
|
|
- pcon = context_new(process_context);
|
|
- freecon(process_context);
|
|
- process_level = strdup(context_range_get(pcon));
|
|
- context_free(pcon);
|
|
-
|
|
- if (debug)
|
|
- pam_syslog (pamh, LOG_DEBUG, "process level=%s", process_level);
|
|
-
|
|
- ucon = context_new(orig_context);
|
|
-
|
|
- context_range_set(ucon, process_level);
|
|
- free(process_level);
|
|
-
|
|
- if (!mls_range_allowed(pamh, orig_context, context_str(ucon), debug)) {
|
|
- send_text(pamh, _("Requested MLS level not in permitted range"), debug);
|
|
- /* even if default_user_context is NULL audit that anyway */
|
|
- send_audit_message(pamh, 0, default_user_context, context_str(ucon));
|
|
- context_free(ucon);
|
|
- return PAM_AUTH_ERR;
|
|
- }
|
|
-
|
|
- if (debug)
|
|
- pam_syslog (pamh, LOG_DEBUG, "adjusted context=%s", context_str(ucon));
|
|
-
|
|
- /* replace the user context with the level adjusted one */
|
|
- freecon(user_context);
|
|
- user_context = strdup(context_str(ucon));
|
|
-
|
|
- context_free(ucon);
|
|
- }
|
|
}
|
|
|
|
if (getexeccon(&prev_user_context)<0) {
|
|
@@ -613,7 +697,7 @@ pam_sm_open_session(pam_handle_t *pamh,
|
|
}
|
|
}
|
|
}
|
|
- if(ttys && tty ) {
|
|
+ if (ttys && tty) {
|
|
ttyn=strdup(tty);
|
|
ttyn_context=security_label_tty(pamh,ttyn,user_context);
|
|
}
|