diff options
author | Christopher Walker <kris240376@gmail.com> | 2011-03-07 11:21:28 -0300 |
---|---|---|
committer | Niels Horn <niels.horn@slackbuilds.org> | 2011-03-07 11:21:28 -0300 |
commit | e1f98f7da255fb536738ce5087f217695f0b84bc (patch) | |
tree | 2d98ec6610279c914fac410d8e145ccd1a7c5e9e /network | |
parent | 2bc318e85e08f1fad6bc9b36c405dc4e03a6268a (diff) |
network/openvpn-auth-ldap: (LDAP authentication and authorization)
Signed-off-by: Niels Horn <niels.horn@slackbuilds.org>
Diffstat (limited to 'network')
-rw-r--r-- | network/openvpn-auth-ldap/README | 21 | ||||
-rw-r--r-- | network/openvpn-auth-ldap/auth-ldap.patch | 349 | ||||
-rw-r--r-- | network/openvpn-auth-ldap/doinst.sh | 15 | ||||
-rw-r--r-- | network/openvpn-auth-ldap/openvpn-auth-ldap.SlackBuild | 120 | ||||
-rw-r--r-- | network/openvpn-auth-ldap/openvpn-auth-ldap.info | 12 | ||||
-rw-r--r-- | network/openvpn-auth-ldap/slack-desc | 19 |
6 files changed, 536 insertions, 0 deletions
diff --git a/network/openvpn-auth-ldap/README b/network/openvpn-auth-ldap/README new file mode 100644 index 0000000000000..228caad46e8f4 --- /dev/null +++ b/network/openvpn-auth-ldap/README @@ -0,0 +1,21 @@ +The OpenVPN Auth-LDAP Plugin implements username/password authentication +via LDAP for OpenVPN 2.x. + +Features + * User authentication against LDAP + * Simple Apache-style configuration file + * LDAP group-based access restrictions + * Integration with the OpenBSD packet filter, support adding and removing + VPN clients from PF tables based on group membership + * Tested against OpenLDAP, the plugin will authenticate any LDAP server + that supports LDAP simple binds -- including Active Directory. + +Building the package + When building this package you will need the source for the OpenVPN + release that is installed on your VPN server. + For Slackware 13.1 this is openvpn-2.1.1, as specified in the .info + file. + This is so openvpn-auth-ldap can build against the OpenVPN plugin header + files for your particular version of OpenVPN. + +Requires the re2c package (can be found on SlackBuilds) diff --git a/network/openvpn-auth-ldap/auth-ldap.patch b/network/openvpn-auth-ldap/auth-ldap.patch new file mode 100644 index 0000000000000..e1cb9e055a5ef --- /dev/null +++ b/network/openvpn-auth-ldap/auth-ldap.patch @@ -0,0 +1,349 @@ +diff -crB auth-ldap-2.0.3/auth-ldap.conf auth-ldap-2.0.3-patched/auth-ldap.conf +*** auth-ldap-2.0.3/auth-ldap.conf 2007-01-22 12:50:42.000000000 -0600 +--- auth-ldap-2.0.3-patched/auth-ldap.conf 2010-06-29 10:58:40.916276380 -0500 +*************** +*** 47,52 **** +--- 47,55 ---- + #PFTable ips_vpn_users + + <Group> ++ # Match full user DN if true, uid only if false ++ RFC2307bis true ++ + BaseDN "ou=Groups,dc=example,dc=com" + SearchFilter "(|(cn=developers)(cn=artists))" + MemberAttribute uniqueMember +diff -crB auth-ldap-2.0.3/src/LFAuthLDAPConfig.m auth-ldap-2.0.3-patched/src/LFAuthLDAPConfig.m +*** auth-ldap-2.0.3/src/LFAuthLDAPConfig.m 2007-01-22 12:50:42.000000000 -0600 +--- auth-ldap-2.0.3-patched/src/LFAuthLDAPConfig.m 2010-06-29 10:58:40.916276380 -0500 +*************** +*** 79,84 **** +--- 79,85 ---- + + /* Group Section Variables */ + LF_GROUP_MEMBER_ATTRIBUTE, /* Group Membership Attribute */ ++ LF_GROUP_MEMBER_RFC2307BIS, /* Look for full DN for user in attribute */ + + /* Misc Shared */ + LF_UNKNOWN_OPCODE, /* Unknown Opcode */ +*************** +*** 146,151 **** +--- 147,153 ---- + static OpcodeTable GroupSectionVariables[] = { + /* name opcode multi required */ + { "MemberAttribute", LF_GROUP_MEMBER_ATTRIBUTE, NO, NO }, ++ { "RFC2307bis", LF_GROUP_MEMBER_RFC2307BIS, NO, NO }, + { NULL, 0 } + }; + +*************** +*** 696,707 **** +--- 698,719 ---- + + switch(opcodeEntry->opcode) { + TRLDAPGroupConfig *config; ++ BOOL memberRFC2307BIS; + + case LF_GROUP_MEMBER_ATTRIBUTE: + config = [self currentSectionContext]; + [config setMemberAttribute: [value string]]; + break; + ++ case LF_GROUP_MEMBER_RFC2307BIS: ++ config = [self currentSectionContext]; ++ if (![value boolValue: &memberRFC2307BIS]) { ++ [self errorBoolValue: value]; ++ return; ++ } ++ [config setMemberRFC2307BIS: memberRFC2307BIS]; ++ break; ++ + case LF_LDAP_BASEDN: + config = [self currentSectionContext]; + [config setBaseDN: [value string]]; +diff -crB auth-ldap-2.0.3/src/LFLDAPConnection.h auth-ldap-2.0.3-patched/src/LFLDAPConnection.h +*** auth-ldap-2.0.3/src/LFLDAPConnection.h 2007-01-22 12:50:42.000000000 -0600 +--- auth-ldap-2.0.3-patched/src/LFLDAPConnection.h 2010-06-29 10:58:40.920285882 -0500 +*************** +*** 56,61 **** +--- 56,62 ---- + baseDN: (LFString *) base + attributes: (TRArray *) attributes; + - (BOOL) compareDN: (LFString *) dn withAttribute: (LFString *) attribute value: (LFString *) value; ++ - (BOOL) compare: (LFString *) dn withAttribute: (LFString *) attribute value: (LFString *) value; + + - (BOOL) setReferralEnabled: (BOOL) enabled; + - (BOOL) setTLSCACertFile: (LFString *) fileName; +diff -crB auth-ldap-2.0.3/src/LFLDAPConnection.m auth-ldap-2.0.3-patched/src/LFLDAPConnection.m +*** auth-ldap-2.0.3/src/LFLDAPConnection.m 2007-03-22 15:09:51.000000000 -0500 +--- auth-ldap-2.0.3-patched/src/LFLDAPConnection.m 2010-06-29 10:58:40.920285882 -0500 +*************** +*** 405,410 **** +--- 405,454 ---- + return NO; + } + ++ - (BOOL) compare: (LFString *) dn withAttribute: (LFString *) attribute value: (LFString *) value { ++ struct timeval timeout; ++ LDAPMessage *res; ++ struct berval bval; ++ int err; ++ int msgid; ++ ++ /* Set up the ber structure for our value */ ++ bval.bv_val = (char *) [value cString]; ++ bval.bv_len = [value length] - 1; /* Length includes NULL terminator */ ++ ++ /* Set up the timeout */ ++ timeout.tv_sec = _timeout; ++ timeout.tv_usec = 0; ++ ++ /* Perform the compare */ ++ if ((err = ldap_compare_ext(ldapConn, [dn cString], [attribute cString], &bval, NULL, NULL, &msgid)) != LDAP_SUCCESS) { ++ [TRLog debug: "LDAP compare failed: %d: %s", err, ldap_err2string(err)]; ++ return NO; ++ } ++ ++ /* Wait for the result */ ++ if (ldap_result(ldapConn, msgid, 1, &timeout, &res) == -1) { ++ err = ldap_get_errno(ldapConn); ++ if (err == LDAP_TIMEOUT) ++ ldap_abandon_ext(ldapConn, msgid, NULL, NULL); ++ ++ [TRLog debug: "ldap_compare_ext failed: %s", ldap_err2string(err)]; ++ return NO; ++ } ++ ++ /* Check the result */ ++ if (ldap_parse_result(ldapConn, res, &err, NULL, NULL, NULL, NULL, 1) != LDAP_SUCCESS) { ++ /* Parsing failed */ ++ return NO; ++ } ++ if (err == LDAP_COMPARE_TRUE) ++ return YES; ++ else ++ return NO; ++ ++ return NO; ++ } ++ + + - (BOOL) _setLDAPOption: (int) opt value: (const char *) value connection: (LDAP *) ldapConn { + int err; +diff -crB auth-ldap-2.0.3/src/TRLDAPEntry.h auth-ldap-2.0.3-patched/src/TRLDAPEntry.h +*** auth-ldap-2.0.3/src/TRLDAPEntry.h 2006-07-25 18:55:47.000000000 -0500 +--- auth-ldap-2.0.3-patched/src/TRLDAPEntry.h 2010-06-29 10:58:40.920285882 -0500 +*************** +*** 40,50 **** +--- 40,53 ---- + + @interface TRLDAPEntry : TRObject { + LFString *_dn; ++ LFString *_rdn; + TRHash *_attributes; + } + + - (id) initWithDN: (LFString *) dn attributes: (TRHash *) attributes; + - (LFString *) dn; ++ - (LFString *) rdn; ++ - (void) setRDN: (LFString *) rdn; + - (TRHash *) attributes; + + @end +diff -crB auth-ldap-2.0.3/src/TRLDAPEntry.m auth-ldap-2.0.3-patched/src/TRLDAPEntry.m +*** auth-ldap-2.0.3/src/TRLDAPEntry.m 2006-07-25 18:55:47.000000000 -0500 +--- auth-ldap-2.0.3-patched/src/TRLDAPEntry.m 2010-06-29 10:58:40.920285882 -0500 +*************** +*** 42,47 **** +--- 42,48 ---- + return self; + + _dn = [dn retain]; ++ _rdn = nil; + _attributes = [attributes retain]; + + return self; +*************** +*** 49,54 **** +--- 50,56 ---- + + - (void) dealloc { + [_dn release]; ++ [_rdn release]; + [_attributes release]; + [super dealloc]; + } +*************** +*** 57,62 **** +--- 59,72 ---- + return _dn; + } + ++ - (LFString *) rdn { ++ return _rdn; ++ } ++ ++ - (void) setRDN: (LFString *) rdn { ++ _rdn=rdn; ++ } ++ + - (TRHash *) attributes { + return _attributes; + } +diff -crB auth-ldap-2.0.3/src/TRLDAPGroupConfig.h auth-ldap-2.0.3-patched/src/TRLDAPGroupConfig.h +*** auth-ldap-2.0.3/src/TRLDAPGroupConfig.h 2006-07-30 15:19:54.000000000 -0500 +--- auth-ldap-2.0.3-patched/src/TRLDAPGroupConfig.h 2010-06-29 10:58:40.920285882 -0500 +*************** +*** 42,47 **** +--- 42,48 ---- + LFString *_baseDN; + LFString *_searchFilter; + LFString *_memberAttribute; ++ BOOL _memberRFC2307BIS; + LFString *_pfTable; + } + +*************** +*** 54,59 **** +--- 55,63 ---- + - (LFString *) memberAttribute; + - (void) setMemberAttribute: (LFString *) memberAttribute; + ++ - (BOOL) memberRFC2307BIS; ++ - (void) setMemberRFC2307BIS: (BOOL) memberRFC2307BIS; ++ + - (LFString *) pfTable; + - (void) setPFTable: (LFString *) tableName; + +diff -crB auth-ldap-2.0.3/src/TRLDAPGroupConfig.m auth-ldap-2.0.3-patched/src/TRLDAPGroupConfig.m +*** auth-ldap-2.0.3/src/TRLDAPGroupConfig.m 2006-07-30 15:19:54.000000000 -0500 +--- auth-ldap-2.0.3-patched/src/TRLDAPGroupConfig.m 2010-06-29 10:58:40.920285882 -0500 +*************** +*** 81,86 **** +--- 81,94 ---- + _memberAttribute = [memberAttribute retain]; + } + ++ - (BOOL) memberRFC2307BIS { ++ return (_memberRFC2307BIS); ++ } ++ ++ - (void) setMemberRFC2307BIS: (BOOL) memberRFC2307BIS { ++ _memberRFC2307BIS = memberRFC2307BIS; ++ } ++ + - (void) setPFTable: (LFString *) tableName { + if (_pfTable) + [_pfTable release]; +diff -crB auth-ldap-2.0.3/src/auth-ldap.m auth-ldap-2.0.3-patched/src/auth-ldap.m +*** auth-ldap-2.0.3/src/auth-ldap.m 2007-01-22 12:50:42.000000000 -0600 +--- auth-ldap-2.0.3-patched/src/auth-ldap.m 2010-06-29 11:02:14.680387830 -0500 +*************** +*** 307,320 **** + goto error; + } + +- /* Bind if requested */ +- if ([config bindDN]) { +- if (![ldap bindWithDN: [config bindDN] password: [config bindPassword]]) { +- [TRLog error: "Unable to bind as %s", [[config bindDN] cString]]; +- goto error; +- } +- } +- + /* Certificate file */ + if ((value = [config tlsCACertFile])) + if (![ldap setTLSCACertFile: value]) +--- 307,312 ---- +*************** +*** 340,345 **** +--- 332,345 ---- + if (![ldap startTLS]) + goto error; + ++ /* Bind if requested */ ++ if ([config bindDN]) { ++ if (![ldap bindWithDN: [config bindDN] password: [config bindPassword]]) { ++ [TRLog error: "Unable to bind as %s", [[config bindDN] cString]]; ++ goto error; ++ } ++ } ++ + return ldap; + + error: +*************** +*** 409,414 **** +--- 409,415 ---- + TREnumerator *entryIter; + TRLDAPEntry *entry; + TRLDAPGroupConfig *result = nil; ++ int userNameLength; + + /* + * Groups are loaded into the array in the order that they are listed +*************** +*** 426,440 **** + /* Error occured, all stop */ + if (!ldapEntries) + break; +! +! /* Iterate over the returned entries */ +! entryIter = [ldapEntries objectEnumerator]; +! while ((entry = [entryIter nextObject]) != nil) { +! if ([ldap compareDN: [entry dn] withAttribute: [groupConfig memberAttribute] value: [ldapUser dn]]) { +! /* Group match! */ +! result = groupConfig; + } + } + [entryIter release]; + [ldapEntries release]; + if (result) +--- 427,453 ---- + /* Error occured, all stop */ + if (!ldapEntries) + break; +! if ([groupConfig memberRFC2307BIS]) { +! /* Iterate over the returned entries */ +! entryIter = [ldapEntries objectEnumerator]; +! +! while ((entry = [entryIter nextObject]) != nil) { +! if ([ldap compareDN: [entry dn] withAttribute: [groupConfig memberAttribute] value: [ldapUser dn]]) { +! /* Group match! */ +! result = groupConfig; +! } +! } +! } else { +! /* Iterate over the returned entries */ +! entryIter = [ldapEntries objectEnumerator]; +! while ((entry = [entryIter nextObject]) != nil) { +! if ([ldap compare: [entry dn] withAttribute: [groupConfig memberAttribute] value: [ldapUser rdn]]) { +! /* Group match! */ +! result = groupConfig; +! } + } + } ++ + [entryIter release]; + [ldapEntries release]; + if (result) +*************** +*** 551,556 **** +--- 564,570 ---- + int ret = OPENVPN_PLUGIN_FUNC_ERROR; + + username = get_env("username", envp); ++ LFString *userName=[[LFString alloc]initWithCString: username]; + password = get_env("password", envp); + remoteAddress = get_env("ifconfig_pool_remote_ip", envp); + +*************** +*** 568,573 **** +--- 582,588 ---- + + /* Find the user record */ + ldapUser = find_ldap_user(ldap, ctx->config, username); ++ [ldapUser setRDN: userName]; + if (!ldapUser) { + /* No such user. */ + [TRLog warning: "LDAP user \"%s\" was not found.", username]; diff --git a/network/openvpn-auth-ldap/doinst.sh b/network/openvpn-auth-ldap/doinst.sh new file mode 100644 index 0000000000000..90f1068f774b0 --- /dev/null +++ b/network/openvpn-auth-ldap/doinst.sh @@ -0,0 +1,15 @@ +config() { + NEW="$1" + OLD="$(dirname $NEW)/$(basename $NEW .new)" + + # If there's no config file by that name, mv it over: + if [ ! -r $OLD ]; then + mv $NEW $OLD + elif [ "$(cat $OLD | md5sum)" = "$(cat $NEW | md5sum)" ]; then # toss the redundant copy + rm $NEW + fi + # Otherwise, we leave the .new copy for the admin to consider... +} + +config etc/openvpn/auth-ldap.conf.new + diff --git a/network/openvpn-auth-ldap/openvpn-auth-ldap.SlackBuild b/network/openvpn-auth-ldap/openvpn-auth-ldap.SlackBuild new file mode 100644 index 0000000000000..b819a6a43400a --- /dev/null +++ b/network/openvpn-auth-ldap/openvpn-auth-ldap.SlackBuild @@ -0,0 +1,120 @@ +#!/bin/sh + +# Slackware build script for openvpn-auth-ldap + +# Copyright (c) 2009 Chris Walker <kris240376@gmail.com> +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of the {company} nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +PRGNAM=openvpn-auth-ldap +VERSION=${VERSION:-2.0.3} +BUILD=${BUILD:-1} +TAG=${TAB:-_SBo} + +if [ -z "$ARCH" ]; then + case "$( uname -m )" in + i?86) ARCH=i486 ;; + arm*) ARCH=arm ;; + *) ARCH=$( uname -m ) ;; + esac +fi + +CWD=$(pwd) +TMP=${TMP:-/tmp/SBo} +PKG=$TMP/package-$PRGNAM +OUTPUT=${OUTPUT:-/tmp} + +VPNVERSION=${VPNVERSION:-2.1.1} + +if [ "$ARCH" = "i486" ]; then + SLKCFLAGS="-O2 -march=i486 -mtune=i686" + LIBDIRSUFFIX="" +elif [ "$ARCH" = "i686" ]; then + SLKCFLAGS="-O2 -march=i686 -mtune=i686" + LIBDIRSUFFIX="" +elif [ "$ARCH" = "x86_64" ]; then + SLKCFLAGS="-O2 -fPIC" + LIBDIRSUFFIX="64" +else + SLKCFLAGS="-O2" + LIBDIRSUFFIX="" +fi + +if [ ! -f $CWD/openvpn-$VPNVERSION.tar.gz ]; then + echo ; echo "Missing OpenVPN source. This package depends on the OpenVPN" + echo "source being available at build time." + echo "Exiting..." ; echo ; exit 1 +fi + +set -e + +rm -rf $PKG +mkdir -p $TMP $PKG $OUTPUT +cd $TMP +rm -rf openvpn-$VPNVERSION +rm -rf auth-ldap-$VERSION +tar xvf $CWD/openvpn-$VPNVERSION.tar.gz +tar xvf $CWD/auth-ldap-$VERSION.tar.gz +cd auth-ldap-$VERSION +chown -R root:root . +chmod -R a-s,u+w,go+r-w . + +patch -p1 < $CWD/auth-ldap.patch + +CFLAGS="$SLKCFLAGS" \ +CXXFLAGS="$SLKCFLAGS" \ +./configure \ + --prefix=/usr \ + --libdir=/usr/lib${LIBDIRSUFFIX} \ + --with-openldap=/usr/libexec \ + --with-openvpn=$TMP/openvpn-$VPNVERSION \ + --build=$ARCH-slackware-linux + +make +mkdir -p $PKG/usr/lib${LIBDIRSUFFIX} $PKG/usr/man +make install DESTDIR=$PKG + +# remove empty man dir (yes, needed at build time) +rmdir $PKG/usr/man + +find $PKG | xargs file | grep -e "executable" -e "shared object" | grep ELF \ + | cut -f 1 -d : | xargs strip --strip-unneeded 2> /dev/null || true + +mkdir -p $PKG/etc/openvpn +cp auth-ldap.conf $PKG/etc/openvpn/auth-ldap.conf.new + +mkdir -p $PKG/usr/doc/$PRGNAM-$VERSION +cp -a LICENSE README $PKG/usr/doc/$PRGNAM-$VERSION +cat $CWD/$PRGNAM.SlackBuild > $PKG/usr/doc/$PRGNAM-$VERSION/$PRGNAM.SlackBuild + +mkdir $PKG/install +cat $CWD/slack-desc > $PKG/install/slack-desc +cat $CWD/doinst.sh > $PKG/install/doinst.sh + +cd $PKG +/sbin/makepkg -l y -c n $OUTPUT/$PRGNAM-$VERSION-$ARCH-$BUILD$TAG.${PKGTYPE:-tgz} diff --git a/network/openvpn-auth-ldap/openvpn-auth-ldap.info b/network/openvpn-auth-ldap/openvpn-auth-ldap.info new file mode 100644 index 0000000000000..715a58ade7aba --- /dev/null +++ b/network/openvpn-auth-ldap/openvpn-auth-ldap.info @@ -0,0 +1,12 @@ +PRGNAM="openvpn-auth-ldap" +VERSION="2.0.3" +HOMEPAGE="http://code.google.com/p/openvpn-auth-ldap/" +DOWNLOAD="http://openvpn-auth-ldap.googlecode.com/files/auth-ldap-2.0.3.tar.gz \ + http://openvpn.net/release/openvpn-2.1.1.tar.gz" +MD5SUM="03dedc57efc8d4fc2ffe2c014121299d \ + b273ed2b5ec8616fb9834cde8634bce7" +DOWNLOAD_x86_64="" +MD5SUM_x86_64="" +MAINTAINER="Christopher Walker" +EMAIL="kris240376@gmail.com" +APPROVED="Niels Horn" diff --git a/network/openvpn-auth-ldap/slack-desc b/network/openvpn-auth-ldap/slack-desc new file mode 100644 index 0000000000000..9fc3610917bf0 --- /dev/null +++ b/network/openvpn-auth-ldap/slack-desc @@ -0,0 +1,19 @@ +# HOW TO EDIT THIS FILE: +# The "handy ruler" below makes it easier to edit a package description. Line +# up the first '|' above the ':' following the base package name, and the '|' +# on the right side marks the last column you can put a character in. You must +# make exactly 11 lines for the formatting to be correct. It's also +# customary to leave one space after the ':'. + + |-----handy-ruler------------------------------------------------------| +openvpn-auth-ldap: openvpn-auth-ldap (LDAP authentication and authorization plugin) +openvpn-auth-ldap: +openvpn-auth-ldap: The OpenVPN Auth-LDAP Plugin implements username/password +openvpn-auth-ldap: authentication via LDAP for OpenVPN 2.x. +openvpn-auth-ldap: +openvpn-auth-ldap: Homepage: http://code-google.com/p/openvpn-auth-ldap/ +openvpn-auth-ldap: +openvpn-auth-ldap: +openvpn-auth-ldap: +openvpn-auth-ldap: +openvpn-auth-ldap: |