647 lines
18 KiB
C
647 lines
18 KiB
C
/*
|
|
******************************************************************************
|
|
* INTERPEAK EXAMPLE SOURCE FILE
|
|
*
|
|
* Document no: @(#) $Name: VXWORKS_ITER40_2019102207 $ $RCSfile: ipppp_example.c,v $ $Revision: 1.25.10.2 $
|
|
* $Source: /home/interpeak/CVSRoot/ipppp/config/ipppp_example.c,v $
|
|
* $Author: svc-cmnet $
|
|
* $State: Exp $ $Locker: $
|
|
*
|
|
* INTERPEAK_COPYRIGHT_STRING
|
|
* Design and implementation by Lennart Bang <lob@interpeak.se>
|
|
******************************************************************************
|
|
*/
|
|
|
|
|
|
/*
|
|
****************************************************************************
|
|
* 1 DESCRIPTION
|
|
****************************************************************************
|
|
* Example file of PPP action callback.
|
|
*
|
|
*/
|
|
|
|
/*
|
|
****************************************************************************
|
|
* 2 CONFIGURATION
|
|
****************************************************************************
|
|
*/
|
|
|
|
#ifdef IP_PORT_VXWORKS
|
|
|
|
#ifdef IPCOM_USE_CLIB_PROTO
|
|
#undef IPCOM_USE_CLIB_PROTO
|
|
#endif
|
|
|
|
#endif
|
|
|
|
#ifndef IPCOM_KERNEL
|
|
#define IPCOM_KERNEL
|
|
#endif
|
|
#include "ipppp_config.h"
|
|
|
|
|
|
/*
|
|
****************************************************************************
|
|
* 3 INCLUDE FILES
|
|
****************************************************************************
|
|
*/
|
|
|
|
#ifdef IP_PORT_VXWORKS
|
|
|
|
#ifdef INCLUDE_IPCOM_USE_SYSLOGD
|
|
#define IPCOM_USE_SYSLOG
|
|
#endif
|
|
|
|
#include <ipcom_err.h>
|
|
|
|
/*
|
|
* some internal macro mappings - ugly but necessary for Ipcom_netif
|
|
* structure alignment!
|
|
*/
|
|
#ifdef INCLUDE_IPPPP
|
|
#define IPPPP
|
|
#endif
|
|
|
|
#ifdef INCLUDE_IP_SECURITY
|
|
#define IPIPSEC2
|
|
#endif
|
|
|
|
#ifdef INCLUDE_IPNET_USE_WLAN
|
|
#define IPWLAN
|
|
#endif
|
|
|
|
#include <ipcom_netif.h>
|
|
#include <ipcom_syslog.h>
|
|
#include <ipcom_clib.h>
|
|
#include <ipcom_err.h>
|
|
#include "ipppp.h"
|
|
|
|
#else
|
|
|
|
#define IPCOM_USE_CLIB_PROTO
|
|
#include <ipcom_clib.h>
|
|
#include <ipcom_cstyle.h>
|
|
#include <ipcom_list.h>
|
|
#include <ipcom_netif.h>
|
|
#include <ipcom_syslog.h>
|
|
#include <ipcom_type.h>
|
|
|
|
#include "ipppp_h.h"
|
|
|
|
#endif /* IP_PORT_VXWORKS */
|
|
|
|
/*
|
|
****************************************************************************
|
|
* 4 DEFINES
|
|
****************************************************************************
|
|
*/
|
|
|
|
#if (defined(IP_PORT_VXWORKS) && (defined(INCLUDE_IPPPP) || defined(INCLUDE_IPPPPOE))) || !defined(IP_PORT_VXWORKS)
|
|
|
|
/*
|
|
****************************************************************************
|
|
* 5 TYPES
|
|
****************************************************************************
|
|
*/
|
|
|
|
#ifdef IP_PORT_VXWORKS
|
|
|
|
/* typedef to make compiler happy... */
|
|
typedef int (*ipppp_action_cb)(struct Ipppp_action_work *w);
|
|
|
|
/* prototype user-defined action callback routine */
|
|
#if PPP_INSTALL_CALLBACK_HOOK == TRUE
|
|
IP_EXTERN int PPP_ACTION_CALLBACK_HOOK (struct Ipppp_action_work *w);
|
|
#endif
|
|
|
|
#else
|
|
|
|
struct Ipppp_action_work
|
|
{
|
|
Ipcom_list list;
|
|
char ifname[IP_IFNAMSIZ];
|
|
Ip_u32 ifindex;
|
|
Ip_u32 link_index;
|
|
int action;
|
|
void *data;
|
|
};
|
|
|
|
#endif
|
|
/*
|
|
****************************************************************************
|
|
* 6 EXTERN PROTOTYPES
|
|
****************************************************************************
|
|
*/
|
|
|
|
/*
|
|
****************************************************************************
|
|
* 7 LOCAL PROTOTYPES
|
|
****************************************************************************
|
|
*/
|
|
|
|
/*
|
|
****************************************************************************
|
|
* 8 DATA
|
|
****************************************************************************
|
|
*/
|
|
|
|
|
|
#ifdef IP_PORT_VXWORKS
|
|
|
|
Ipcom_once_t ipppp_work_once = {IPCOM_ONCE_STATE_UNINITIALIZED, 0, IP_NULL};
|
|
IP_EXTERN Ipcom_mutex ipppp_work_lock;
|
|
IP_EXTERN Ipcom_sem ipppp_work_sem;
|
|
IP_EXTERN Ipcom_list ipppp_work_queue;
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipppp_example_action_work
|
|
*===========================================================================
|
|
* Description:
|
|
* Parameters:
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_STATIC int
|
|
ipppp_example_action_work(struct Ipppp_action_work *w)
|
|
{
|
|
ipppp_action_cb ppp_cb;
|
|
|
|
#if PPP_INSTALL_CALLBACK_HOOK == TRUE
|
|
ppp_cb = (PPP_ACTION_CALLBACK_HOOK == NULL) ?
|
|
ipppp_example_action_work_default : PPP_ACTION_CALLBACK_HOOK;
|
|
#else
|
|
ppp_cb = ipppp_example_action_work_default;
|
|
#endif
|
|
|
|
return ppp_cb(w);
|
|
}
|
|
|
|
#else /* (not vxWorks) */
|
|
|
|
Ipcom_once_t ipppp_work_once = IPCOM_ONCE_INIT;
|
|
Ipcom_mutex ipppp_work_lock = IPCOM_MUTEX_INVALID;
|
|
Ipcom_sem ipppp_work_sem = IPCOM_SEM_INVALID;
|
|
Ipcom_list ipppp_work_queue;
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipppp_auth_login_hook
|
|
*===========================================================================
|
|
* Description:
|
|
* Parameters:
|
|
* Returns:
|
|
*
|
|
*/
|
|
#ifdef IPPPP_USE_AUTH
|
|
IP_STATIC void
|
|
ipppp_auth_login_hook(Ip_err retval, Ipcom_auth_userinfo *userinfo, void *auth_login_cookie)
|
|
{
|
|
struct Ip_pppreq pppreq;
|
|
Ipppp_auth_cookie *auth_cookie = auth_login_cookie;
|
|
int ret;
|
|
Ip_fd fd;
|
|
|
|
if (auth_login_cookie == IP_NULL)
|
|
return;
|
|
|
|
if (ipcom_if_indextoname(auth_cookie->ifindex, pppreq.pppr_name) == IP_NULL)
|
|
{
|
|
IPCOM_LOG1(WARNING, "<if=%d> :: ipppp_auth_login_hook() :: missing interface",
|
|
auth_cookie->ifindex);
|
|
goto leave;
|
|
}
|
|
|
|
#ifdef IPCOM_USE_INET
|
|
fd = ipcom_socket(IP_AF_INET, IP_SOCK_DGRAM, 0);
|
|
#else
|
|
fd = ipcom_socket(IP_AF_INET6, IP_SOCK_DGRAM, 0);
|
|
#endif
|
|
if (fd == IP_INVALID_SOCKET)
|
|
{
|
|
IP_PANIC();
|
|
goto leave;
|
|
}
|
|
|
|
pppreq.pppru.auth.ifindex = auth_cookie->ifindex;
|
|
pppreq.pppru.auth.link_index = auth_cookie->link_index;
|
|
pppreq.pppru.auth.success = retval == IPCOM_SUCCESS;
|
|
ipcom_memcpy(&pppreq.pppru.auth.userinfo, userinfo, sizeof(Ipcom_auth_userinfo));
|
|
|
|
ret = ipcom_socketioctl(fd, IP_SIOCXPPPAUTH, &pppreq);
|
|
if (ret != 0)
|
|
{
|
|
IPCOM_LOG3(WARNING, "<%s> :: ipppp_auth_login_hook() :: ioctl(AUTH) failed : %s(%d)",
|
|
pppreq.pppr_name, ipcom_strerror(ipcom_errno), ipcom_errno);
|
|
}
|
|
|
|
(void)ipcom_socketclose(fd);
|
|
leave:
|
|
ipcom_free(auth_cookie);
|
|
}
|
|
#endif /* IPPPP_USE_AUTH */
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipppp_example_action_work
|
|
*===========================================================================
|
|
* Description:
|
|
* Parameters:
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_STATIC void
|
|
ipppp_example_action_work(struct Ipppp_action_work *w)
|
|
{
|
|
struct Ip_pppreq pppreq;
|
|
struct Ip_ifreq ifreq;
|
|
Ip_fd fd;
|
|
int ret;
|
|
int action = w->action;
|
|
void *data = w->data;
|
|
|
|
ipcom_strcpy(ifreq.ifr_name, w->ifname);
|
|
ipcom_strcpy(pppreq.pppr_name, w->ifname);
|
|
|
|
#ifdef IPCOM_USE_INET
|
|
fd = ipcom_socket(IP_AF_INET, IP_SOCK_DGRAM, 0);
|
|
#else
|
|
fd = ipcom_socket(IP_AF_INET6, IP_SOCK_DGRAM, 0);
|
|
#endif
|
|
if (fd == IP_INVALID_SOCKET)
|
|
{
|
|
IP_PANIC();
|
|
return;
|
|
}
|
|
|
|
switch(action)
|
|
{
|
|
|
|
/********************* ACTION INIT ********************/
|
|
case IPPPP_ACTION_INIT:
|
|
IPCOM_LOG1(INFO, "<%s> :: ACTION init", w->ifname);
|
|
|
|
/* Get PPP flags. */
|
|
if (ipcom_socketioctl(fd, IP_SIOCXPPPGFLAGS, &pppreq) == IP_SOCKERR)
|
|
{
|
|
IP_PANIC();
|
|
break;
|
|
}
|
|
|
|
/****** PPP Example configuration *****/
|
|
|
|
/* Set PPP flags. */
|
|
ipcom_strcpy(pppreq.pppr_name, w->ifname);
|
|
if (ipcom_socketioctl(fd, IP_SIOCXPPPSFLAGS, &pppreq) == IP_SOCKERR)
|
|
{
|
|
IP_PANIC();
|
|
break;
|
|
}
|
|
break;
|
|
|
|
|
|
/********************* LCP STARTING ********************/
|
|
case IPPPP_ACTION_LCP_STARTING:
|
|
IPCOM_LOG1(INFO, "<%s> :: ACTION LCP starting", w->ifname);
|
|
break;
|
|
|
|
|
|
#ifdef IPCOM_USE_INET
|
|
/********************* IPCP STARTING ********************/
|
|
case IPPPP_ACTION_IPCP_STARTING:
|
|
IPCOM_LOG1(INFO, "<%s> :: ACTION IPCP starting", w->ifname);
|
|
/* Example of how to set local address if "ipppp.addr" sysvar is not used:
|
|
{
|
|
struct Ip_sockaddr_in *sinaddr;
|
|
sinaddr = (struct Ip_sockaddr_in *)&ifreq.ip_ifr_addr;
|
|
(void)ipcom_inet_pton(IP_AF_INET, "12.0.0.0", &sinaddr->sin_addr);
|
|
sinaddr->sin_family = IP_AF_INET;
|
|
sinaddr->sin_len = sizeof(struct Ip_sockaddr_in);
|
|
sinaddr->sin_addr.s_addr = ip_htonl((ip_ntohl(sinaddr->sin_addr.s_addr) + netif->ifindex));
|
|
(void)ipcom_socketioctl(fd, IP_SIOCSIFADDR, &ifreq);
|
|
}
|
|
*/
|
|
break;
|
|
|
|
/********************* IP UP ********************/
|
|
case IPPPP_ACTION_IP_UP:
|
|
IPCOM_LOG1(DEBUG, "<%s> :: ACTION IP up", w->ifname);
|
|
break;
|
|
|
|
/********************* IP DOWN ********************/
|
|
case IPPPP_ACTION_IP_DOWN:
|
|
IPCOM_LOG1(DEBUG, "<%s> :: ACTION IP down", w->ifname);
|
|
break;
|
|
#endif /* IPCOM_USE_INET */
|
|
|
|
|
|
#ifdef IPCOM_USE_INET6
|
|
/********************* IPV6 STARTING ********************/
|
|
case IPPPP_ACTION_IPV6CP_STARTING:
|
|
IPCOM_LOG1(INFO, "<%s> :: ACTION IPV6CP starting", w->ifname);
|
|
break;
|
|
|
|
/********************* IPV6 UP ********************/
|
|
case IPPPP_ACTION_IPV6_UP:
|
|
IPCOM_LOG1(DEBUG, "<%s> :: ACTION IPV6 up", w->ifname);
|
|
break;
|
|
|
|
/********************* IPV6 DOWN ********************/
|
|
case IPPPP_ACTION_IPV6_DOWN:
|
|
IPCOM_LOG1(DEBUG, "<%s> :: ACTION IPV6 down", w->ifname);
|
|
break;
|
|
|
|
/********************* IPV6 PEER ID ********************/
|
|
case IPPPP_ACTION_PEER_IDENTIFIER:
|
|
IPCOM_LOG1(DEBUG, "<%s> :: ACTION PEER ID", w->ifname);
|
|
break;
|
|
#endif /* IPCOM_USE_INET6 */
|
|
|
|
|
|
/********************* ACTION LOGIN ********************/
|
|
/* Called when we need to authenticate ourselves (i.e. login) to the peer.
|
|
* User code must call ioctl(IP_SIOCXPPPLOGIN) with the same 'data',
|
|
* username and password when ready to login.
|
|
*/
|
|
#ifdef IPPPP_USE_AUTH
|
|
case IPPPP_ACTION_LOGIN:
|
|
{
|
|
Ip_size_t password_size = IPCOM_AUTH_PASSWORD_MAX;
|
|
Ip_size_t username_size = IPCOM_AUTH_USERNAME_MAX;
|
|
|
|
IPCOM_LOG1(INFO, "<%s> :: ACTION login", w->ifname);
|
|
|
|
if (ipcom_sysvar_get("ipppp.username", pppreq.pppru.login.username, &username_size)
|
|
&& username_size > 0
|
|
&& ipcom_sysvar_get("ipppp.password", pppreq.pppru.login.password, &password_size)
|
|
&& password_size > 0)
|
|
{
|
|
pppreq.pppru.login.ifindex = w->ifindex;
|
|
pppreq.pppru.login.link_index = w->link_index;
|
|
pppreq.pppru.login.login_cookie = data;
|
|
|
|
ret = ipcom_socketioctl(fd, IP_SIOCXPPPLOGIN, &pppreq);
|
|
if (ret != 0)
|
|
{
|
|
IPCOM_LOG3(WARNING, "<%s> :: ACTION LOGIN :: ioctl(LOGIN) failed : %s(%d)",
|
|
w->ifname, ipcom_strerror(ipcom_errno), ipcom_errno);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
IPCOM_LOG1(NOTICE, "<%s> :: ACTION LOGIN :: missing ppp 'client' username or password", w->ifname);
|
|
}
|
|
}
|
|
break;
|
|
|
|
/********************* ACTION AUTH ********************/
|
|
/* Called when peer needs to be authenticated
|
|
*/
|
|
case IPPPP_ACTION_AUTH:
|
|
{
|
|
Ipcom_auth_userinfo userinfo;
|
|
Ipppp_auth_cookie *auth_cookie = data;
|
|
|
|
IPCOM_LOG1(INFO, "<%s> :: ACTION authenticate", w->ifname);
|
|
|
|
switch(auth_cookie->protocol)
|
|
{
|
|
case IPPPP_PROTOCOL_PAP:
|
|
ret = ipcom_auth_login(auth_cookie->username,
|
|
auth_cookie->password,
|
|
&userinfo,
|
|
ipppp_auth_login_hook,
|
|
auth_cookie);
|
|
break;
|
|
#ifdef IPPPP_USE_AUTH_CHAP
|
|
case IPPPP_PROTOCOL_CHAP:
|
|
{
|
|
Ipcom_auth_chap chap;
|
|
|
|
/* Validate user using the name and CHAP response, id and challenge. */
|
|
chap.response = auth_cookie->chap_response;
|
|
chap.ident = auth_cookie->chap_ident;
|
|
chap.challenge = auth_cookie->chap_challenge;
|
|
chap.challenge_size = auth_cookie->chap_challenge_size;
|
|
|
|
ret = ipcom_auth_chap_login(auth_cookie->username, &chap,
|
|
&userinfo, ipppp_auth_login_hook, auth_cookie);
|
|
}
|
|
break;
|
|
#endif /* IPPPP_USE_AUTH_CHAP */
|
|
default:
|
|
IP_PANIC2();
|
|
ret = IPCOM_ERR_FAILED;
|
|
ipcom_memset(&userinfo, 0, sizeof(userinfo));
|
|
break;
|
|
}
|
|
|
|
switch(ret)
|
|
{
|
|
case IPCOM_ERR_PENDING:
|
|
/* The ipppp_auth_login_hook() will be called later. */
|
|
break;
|
|
default:
|
|
/* login ok/fail. */
|
|
pppreq.pppru.auth.ifindex = w->ifindex;
|
|
pppreq.pppru.auth.link_index = w->link_index;
|
|
pppreq.pppru.auth.success = ret == IPCOM_SUCCESS;
|
|
ipcom_memcpy(&pppreq.pppru.auth.userinfo, &userinfo, sizeof(Ipcom_auth_userinfo));
|
|
ret = ipcom_socketioctl(fd, IP_SIOCXPPPAUTH, &pppreq);
|
|
if (ret != 0)
|
|
{
|
|
IPCOM_LOG3(WARNING, "<%s> :: ACTION AUTH :: ioctl(AUTH) failed : %s(%d)",
|
|
w->ifname, ipcom_strerror(ipcom_errno), ipcom_errno);
|
|
}
|
|
ipcom_free(auth_cookie);
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
#endif /* IPPPP_USE_AUTH */
|
|
|
|
default:
|
|
IP_PANIC();
|
|
break;
|
|
}
|
|
|
|
(void)ipcom_socketclose(fd);
|
|
}
|
|
|
|
#endif /* IP_PORT_VXWORKS */
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipppp_work
|
|
*===========================================================================
|
|
* Description:
|
|
* Parameters:
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_STATIC
|
|
IPCOM_PROCESS(ipppp_work)
|
|
{
|
|
ipcom_proc_init();
|
|
|
|
for (;;)
|
|
{
|
|
struct Ipppp_action_work *work;
|
|
|
|
ipcom_sem_wait(ipppp_work_sem);
|
|
|
|
ipcom_mutex_lock(ipppp_work_lock);
|
|
if ((work = IPCOM_LIST_FIRST(&ipppp_work_queue)) == IP_NULL)
|
|
{
|
|
ipcom_mutex_unlock(ipppp_work_lock);
|
|
continue;
|
|
}
|
|
|
|
ipcom_list_remove(&work->list);
|
|
ipcom_mutex_unlock(ipppp_work_lock);
|
|
|
|
ipppp_example_action_work(work);
|
|
|
|
ipcom_free(work);
|
|
}
|
|
|
|
/* ipcom_proc_exit(); */
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipppp_work_init
|
|
*===========================================================================
|
|
* Description:
|
|
* Parameters:
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_STATIC Ip_err
|
|
ipppp_work_init(void *unused)
|
|
{
|
|
IPCOM_UNUSED_ARG(unused);
|
|
|
|
ipcom_list_init(&ipppp_work_queue);
|
|
ipcom_mutex_create(&ipppp_work_lock);
|
|
ipcom_sem_create(&ipppp_work_sem, 0);
|
|
|
|
return ipcom_proc_create("ipppp_work",
|
|
(Ipcom_proc_func)ipppp_work,
|
|
IPCOM_PROC_STACK_DEFAULT,
|
|
IP_NULL);
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipppp_work_alloc
|
|
*===========================================================================
|
|
* Description:
|
|
* Parameters:
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_STATIC struct Ipppp_action_work *
|
|
ipppp_work_alloc(Ipcom_netif *netif, int action, void *data)
|
|
{
|
|
struct Ipppp_action_work *work;
|
|
|
|
work = ipcom_malloc(sizeof(*work));
|
|
if (work)
|
|
{
|
|
ipcom_strcpy(work->ifname, netif->name);
|
|
work->ifindex = netif->ifindex;
|
|
work->link_index = netif->link_index;
|
|
work->action = action;
|
|
work->data = data;
|
|
}
|
|
|
|
return work;
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipppp_work_schedule
|
|
*===========================================================================
|
|
* Description:
|
|
* Parameters:
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_STATIC int
|
|
ipppp_work_schedule(struct Ipppp_action_work *work)
|
|
{
|
|
if (ipcom_once(&ipppp_work_once,
|
|
ipppp_work_init,
|
|
IP_NULL)
|
|
!= IPCOM_SUCCESS)
|
|
{
|
|
IP_PANIC();
|
|
return -1;
|
|
}
|
|
|
|
|
|
ipcom_mutex_lock(ipppp_work_lock);
|
|
ipcom_list_insert_last(&ipppp_work_queue, &work->list);
|
|
ipcom_mutex_unlock(ipppp_work_lock);
|
|
|
|
ipcom_sem_post(ipppp_work_sem);
|
|
return 0;
|
|
}
|
|
|
|
#endif /* (defined(IP_PORT_VXWORKS) && (defined(INCLUDE_IPPPP) || defined(INCLUDE_IPPPPOE))) || !defined(IP_PORT_VXWORKS) */
|
|
|
|
|
|
/*
|
|
****************************************************************************
|
|
* 10 PUBLIC FUNCTIONS
|
|
****************************************************************************
|
|
*/
|
|
|
|
/*
|
|
* ipppp_example_action_cb - PPP action callback routine
|
|
*
|
|
* In order for PPP to function, this callback function is called by PPP to
|
|
* handle various PPP actions like initialization, startup, peer IPv4 address
|
|
* requests, peer authentication login etc.
|
|
*
|
|
* .IP <netif>
|
|
* Ipcom_netif pointer to the PPP interface.
|
|
* .IP <action>
|
|
* The action type - IPPPP_ACTION_xxx.
|
|
* .IP <data>
|
|
* Action type specific additional data.
|
|
*
|
|
* RETURNS: Depends on the action type.
|
|
*
|
|
* NOMANUAL
|
|
*/
|
|
IP_PUBLIC int
|
|
ipppp_example_action_cb(Ipcom_netif *netif, int action, void *data)
|
|
{
|
|
#if (defined(IP_PORT_VXWORKS) && (defined(INCLUDE_IPPPP) || defined(INCLUDE_IPPPPOE))) || !defined(IP_PORT_VXWORKS)
|
|
struct Ipppp_action_work *work;
|
|
|
|
work = ipppp_work_alloc(netif, action, data);
|
|
|
|
return ipppp_work_schedule(work);
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
|
|
/*
|
|
****************************************************************************
|
|
* END OF FILE
|
|
****************************************************************************
|
|
*/
|
|
|