aboutsummaryrefslogtreecommitdiff
path: root/secmem
diff options
context:
space:
mode:
authorGuido Guenther <agx@sigxcpu.org>2008-06-08 17:28:55 +0200
committerGuido Guenther <agx@sigxcpu.org>2008-06-08 17:28:55 +0200
commit58cc3326be8fa3b841d0596987090d7cb356e101 (patch)
tree9382dca6a9fa5a503bb1f307c902f73d5e7b8653 /secmem
parentImported upstream version 0.6 (diff)
downloadkrb5-auth-dialog-58cc3326be8fa3b841d0596987090d7cb356e101.tar.gz
krb5-auth-dialog-58cc3326be8fa3b841d0596987090d7cb356e101.tar.bz2
krb5-auth-dialog-58cc3326be8fa3b841d0596987090d7cb356e101.zip
Imported Upstream version 0.7.git30891fc
Diffstat (limited to 'secmem')
-rw-r--r--secmem/Makefile.am31
-rw-r--r--secmem/Manifest7
-rw-r--r--secmem/memory.h40
-rw-r--r--secmem/secmem-util.h3
-rw-r--r--secmem/secmem.c448
-rw-r--r--secmem/util.c139
-rw-r--r--secmem/util.h66
7 files changed, 734 insertions, 0 deletions
diff --git a/secmem/Makefile.am b/secmem/Makefile.am
new file mode 100644
index 0000000..331530f
--- /dev/null
+++ b/secmem/Makefile.am
@@ -0,0 +1,31 @@
+# Secure Memory Makefile
+# Copyright (C) 2002 g10 Code GmbH
+#
+# This file is part of PINENTRY.
+#
+# PINENTRY is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# PINENTRY 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 General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+
+## Process this file with automake to produce Makefile.in
+
+EXTRA_DIST = Manifest
+
+noinst_LIBRARIES = libsecmem.a
+
+libsecmem_a_SOURCES = \
+ memory.h \
+ secmem-util.h \
+ util.h \
+ secmem.c \
+ util.c
diff --git a/secmem/Manifest b/secmem/Manifest
new file mode 100644
index 0000000..a37366d
--- /dev/null
+++ b/secmem/Manifest
@@ -0,0 +1,7 @@
+Makefile.am
+memory.h
+secmem-util.h
+secmem.c
+util.c
+util.h
+$names$ iQCVAwUAP+f/RDEAnp832S/7AQIbRQQAzR7UvGOTMl8AWyVgHGQjW5A5fGzRlaEABl+5UpGmzoFGFdP9upHv3Tj0MKETHNRkdOAA5k5QzamDypAr5RINz9rdZPkNPIAtg4csN7Yb6ITJZaH7yLDJcBmhM49a8ZNpDpQeImzpE05cM6TuGVO6NSIrlt9OBhaHfbkpzgr1tI0==6Tyw
diff --git a/secmem/memory.h b/secmem/memory.h
new file mode 100644
index 0000000..c6d04b8
--- /dev/null
+++ b/secmem/memory.h
@@ -0,0 +1,40 @@
+/* Quintuple Agent secure memory allocation
+ * Copyright (C) 1998,1999 Free Software Foundation, Inc.
+ * Copyright (C) 1999,2000 Robert Bihlmeyer <robbe@orcus.priv.at>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef _MEMORY_H
+#define _MEMORY_H
+
+#include <sys/types.h>
+
+/* values for flags, hardcoded in secmem.c */
+#define SECMEM_WARN 0
+#define SECMEM_DONT_WARN 1
+#define SECMEM_SUSPEND_WARN 2
+
+void secmem_init( size_t npool );
+void secmem_term( void );
+void *secmem_malloc( size_t size );
+void *secmem_realloc( void *a, size_t newsize );
+void secmem_free( void *a );
+int m_is_secure( const void *p );
+void secmem_dump_stats(void);
+void secmem_set_flags( unsigned flags );
+unsigned secmem_get_flags(void);
+
+#endif /* _MEMORY_H */
diff --git a/secmem/secmem-util.h b/secmem/secmem-util.h
new file mode 100644
index 0000000..b422182
--- /dev/null
+++ b/secmem/secmem-util.h
@@ -0,0 +1,3 @@
+/* This file exists because "util.h" is such a generic name that it is
+ likely to clash with other such files. */
+#include "util.h"
diff --git a/secmem/secmem.c b/secmem/secmem.c
new file mode 100644
index 0000000..796108d
--- /dev/null
+++ b/secmem/secmem.c
@@ -0,0 +1,448 @@
+/* secmem.c - memory allocation from a secure heap
+ * Copyright (C) 1998, 1999, 2003 Free Software Foundation, Inc.
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * GnuPG 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <unistd.h>
+#if defined(HAVE_MLOCK) || defined(HAVE_MMAP)
+# include <sys/mman.h>
+# include <sys/types.h>
+# include <fcntl.h>
+# ifdef USE_CAPABILITIES
+# include <sys/capability.h>
+# endif
+#endif
+#include <string.h>
+
+#include "memory.h"
+
+#ifdef ORIGINAL_GPG_VERSION
+#include "types.h"
+#include "util.h"
+#else /* ORIGINAL_GPG_VERSION */
+
+#include "util.h"
+
+typedef union {
+ int a;
+ short b;
+ char c[1];
+ long d;
+#ifdef HAVE_U64_TYPEDEF
+ u64 e;
+#endif
+ float f;
+ double g;
+} PROPERLY_ALIGNED_TYPE;
+
+#define log_error log_info
+#define log_bug log_fatal
+
+void
+log_info(char *template, ...)
+{
+ va_list args;
+
+ va_start(args, template);
+ vfprintf(stderr, template, args);
+ va_end(args);
+}
+
+void
+log_fatal(char *template, ...)
+{
+ va_list args;
+
+ va_start(args, template);
+ vfprintf(stderr, template, args);
+ va_end(args);
+ exit(EXIT_FAILURE);
+}
+
+#endif /* ORIGINAL_GPG_VERSION */
+
+#if defined(MAP_ANON) && !defined(MAP_ANONYMOUS)
+# define MAP_ANONYMOUS MAP_ANON
+#endif
+
+#define DEFAULT_POOLSIZE 16384
+
+typedef struct memblock_struct MEMBLOCK;
+struct memblock_struct {
+ unsigned size;
+ union {
+ MEMBLOCK *next;
+ PROPERLY_ALIGNED_TYPE aligned;
+ } u;
+};
+
+
+
+static void *pool;
+static volatile int pool_okay; /* may be checked in an atexit function */
+static int pool_is_mmapped;
+static size_t poolsize; /* allocated length */
+static size_t poollen; /* used length */
+static MEMBLOCK *unused_blocks;
+static unsigned max_alloced;
+static unsigned cur_alloced;
+static unsigned max_blocks;
+static unsigned cur_blocks;
+static int disable_secmem;
+static int show_warning;
+static int no_warning;
+static int suspend_warning;
+
+
+static void
+print_warn(void)
+{
+ if( !no_warning )
+ log_info("Warning: using insecure memory!\n");
+}
+
+
+static void
+lock_pool( void *p, size_t n )
+{
+#if defined(USE_CAPABILITIES) && defined(HAVE_MLOCK)
+ int err;
+
+ cap_set_proc( cap_from_text("cap_ipc_lock+ep") );
+ err = mlock( p, n );
+ if( err && errno )
+ err = errno;
+ cap_set_proc( cap_from_text("cap_ipc_lock+p") );
+
+ if( err ) {
+ if( errno != EPERM
+ #ifdef EAGAIN /* OpenBSD returns this */
+ && errno != EAGAIN
+ #endif
+ )
+ log_error("canīt lock memory: %s\n", strerror(err));
+ show_warning = 1;
+ }
+
+#elif defined(HAVE_MLOCK)
+ uid_t uid;
+ int err;
+
+ uid = getuid();
+
+#ifdef HAVE_BROKEN_MLOCK
+ if( uid ) {
+ errno = EPERM;
+ err = errno;
+ }
+ else {
+ err = mlock( p, n );
+ if( err && errno )
+ err = errno;
+ }
+#else
+ err = mlock( p, n );
+ if( err && errno )
+ err = errno;
+#endif
+
+ if( uid && !geteuid() ) {
+ if( setuid( uid ) || getuid() != geteuid() )
+ log_fatal("failed to reset uid: %s\n", strerror(errno));
+ }
+
+ if( err ) {
+ if( errno != EPERM
+#ifdef EAGAIN /* OpenBSD returns this */
+ && errno != EAGAIN
+#endif
+ )
+ log_error("canīt lock memory: %s\n", strerror(err));
+ show_warning = 1;
+ }
+
+#else
+ log_info("Please note that you don't have secure memory on this system\n");
+#endif
+}
+
+
+static void
+init_pool( size_t n)
+{
+ size_t pgsize;
+
+ poolsize = n;
+
+ if( disable_secmem )
+ log_bug("secure memory is disabled");
+
+#ifdef HAVE_GETPAGESIZE
+ pgsize = getpagesize();
+#else
+ pgsize = 4096;
+#endif
+
+#if HAVE_MMAP
+ poolsize = (poolsize + pgsize -1 ) & ~(pgsize-1);
+# ifdef MAP_ANONYMOUS
+ pool = mmap( 0, poolsize, PROT_READ|PROT_WRITE,
+ MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
+# else /* map /dev/zero instead */
+ { int fd;
+
+ fd = open("/dev/zero", O_RDWR);
+ if( fd == -1 ) {
+ log_error("can't open /dev/zero: %s\n", strerror(errno) );
+ pool = (void*)-1;
+ }
+ else {
+ pool = mmap( 0, poolsize, PROT_READ|PROT_WRITE,
+ MAP_PRIVATE, fd, 0);
+ close (fd);
+ }
+ }
+# endif
+ if( pool == (void*)-1 )
+ log_info("can't mmap pool of %u bytes: %s - using malloc\n",
+ (unsigned)poolsize, strerror(errno));
+ else {
+ pool_is_mmapped = 1;
+ pool_okay = 1;
+ }
+
+#endif
+ if( !pool_okay ) {
+ pool = malloc( poolsize );
+ if( !pool )
+ log_fatal("can't allocate memory pool of %u bytes\n",
+ (unsigned)poolsize);
+ else
+ pool_okay = 1;
+ }
+ lock_pool( pool, poolsize );
+ poollen = 0;
+}
+
+
+/* concatenate unused blocks */
+static void
+compress_pool(void)
+{
+ /* fixme: we really should do this */
+}
+
+void
+secmem_set_flags( unsigned flags )
+{
+ int was_susp = suspend_warning;
+
+ no_warning = flags & 1;
+ suspend_warning = flags & 2;
+
+ /* and now issue the warning if it is not longer suspended */
+ if( was_susp && !suspend_warning && show_warning ) {
+ show_warning = 0;
+ print_warn();
+ }
+}
+
+unsigned
+secmem_get_flags(void)
+{
+ unsigned flags;
+
+ flags = no_warning ? 1:0;
+ flags |= suspend_warning ? 2:0;
+ return flags;
+}
+
+void
+secmem_init( size_t n )
+{
+ if( !n ) {
+#ifdef USE_CAPABILITIES
+ /* drop all capabilities */
+ cap_set_proc( cap_from_text("all-eip") );
+
+#elif !defined(HAVE_DOSISH_SYSTEM)
+ uid_t uid;
+
+ disable_secmem=1;
+ uid = getuid();
+ if( uid != geteuid() ) {
+ if( setuid( uid ) || getuid() != geteuid() )
+ log_fatal("failed to drop setuid\n" );
+ }
+#endif
+ }
+ else {
+ if( n < DEFAULT_POOLSIZE )
+ n = DEFAULT_POOLSIZE;
+ if( !pool_okay )
+ init_pool(n);
+ else
+ log_error("Oops, secure memory pool already initialized\n");
+ }
+}
+
+
+void *
+secmem_malloc( size_t size )
+{
+ MEMBLOCK *mb, *mb2;
+ int compressed=0;
+
+ if( !pool_okay ) {
+ log_info(
+ "operation is not possible without initialized secure memory\n");
+ log_info("(you may have used the wrong program for this task)\n");
+ exit(2);
+ }
+ if( show_warning && !suspend_warning ) {
+ show_warning = 0;
+ print_warn();
+ }
+
+ /* blocks are always a multiple of 32 */
+ size += sizeof(MEMBLOCK);
+ size = ((size + 31) / 32) * 32;
+
+ retry:
+ /* try to get it from the used blocks */
+ for(mb = unused_blocks,mb2=NULL; mb; mb2=mb, mb = mb->u.next )
+ if( mb->size >= size ) {
+ if( mb2 )
+ mb2->u.next = mb->u.next;
+ else
+ unused_blocks = mb->u.next;
+ goto leave;
+ }
+ /* allocate a new block */
+ if( (poollen + size <= poolsize) ) {
+ mb = (void*)((char*)pool + poollen);
+ poollen += size;
+ mb->size = size;
+ }
+ else if( !compressed ) {
+ compressed=1;
+ compress_pool();
+ goto retry;
+ }
+ else
+ return NULL;
+
+ leave:
+ cur_alloced += mb->size;
+ cur_blocks++;
+ if( cur_alloced > max_alloced )
+ max_alloced = cur_alloced;
+ if( cur_blocks > max_blocks )
+ max_blocks = cur_blocks;
+
+ return &mb->u.aligned.c;
+}
+
+
+void *
+secmem_realloc( void *p, size_t newsize )
+{
+ MEMBLOCK *mb;
+ size_t size;
+ void *a;
+
+ mb = (MEMBLOCK*)((char*)p - ((size_t) &((MEMBLOCK*)0)->u.aligned.c));
+ size = mb->size;
+ if( newsize < size )
+ return p; /* it is easier not to shrink the memory */
+ a = secmem_malloc( newsize );
+ memcpy(a, p, size);
+ memset((char*)a+size, 0, newsize-size);
+ secmem_free(p);
+ return a;
+}
+
+
+void
+secmem_free( void *a )
+{
+ MEMBLOCK *mb;
+ size_t size;
+
+ if( !a )
+ return;
+
+ mb = (MEMBLOCK*)((char*)a - ((size_t) &((MEMBLOCK*)0)->u.aligned.c));
+ size = mb->size;
+ /* This does not make much sense: probably this memory is held in the
+ * cache. We do it anyway: */
+ wipememory2(mb, 0xff, size );
+ wipememory2(mb, 0xaa, size );
+ wipememory2(mb, 0x55, size );
+ wipememory2(mb, 0x00, size );
+ mb->size = size;
+ mb->u.next = unused_blocks;
+ unused_blocks = mb;
+ cur_blocks--;
+ cur_alloced -= size;
+}
+
+int
+m_is_secure( const void *p )
+{
+ return p >= pool && p < (void*)((char*)pool+poolsize);
+}
+
+void
+secmem_term()
+{
+ if( !pool_okay )
+ return;
+
+ wipememory2( pool, 0xff, poolsize);
+ wipememory2( pool, 0xaa, poolsize);
+ wipememory2( pool, 0x55, poolsize);
+ wipememory2( pool, 0x00, poolsize);
+#if HAVE_MMAP
+ if( pool_is_mmapped )
+ munmap( pool, poolsize );
+#endif
+ pool = NULL;
+ pool_okay = 0;
+ poolsize=0;
+ poollen=0;
+ unused_blocks=NULL;
+}
+
+
+void
+secmem_dump_stats()
+{
+ if( disable_secmem )
+ return;
+ fprintf(stderr,
+ "secmem usage: %u/%u bytes in %u/%u blocks of pool %lu/%lu\n",
+ cur_alloced, max_alloced, cur_blocks, max_blocks,
+ (ulong)poollen, (ulong)poolsize );
+}
+
diff --git a/secmem/util.c b/secmem/util.c
new file mode 100644
index 0000000..580fd34
--- /dev/null
+++ b/secmem/util.c
@@ -0,0 +1,139 @@
+/* Quintuple Agent
+ * Copyright (C) 1999 Robert Bihlmeyer <robbe@orcus.priv.at>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#define _GNU_SOURCE 1
+
+#include <unistd.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "util.h"
+
+#ifndef TEMP_FAILURE_RETRY
+#define TEMP_FAILURE_RETRY(expression) \
+ (__extension__ \
+ ({ long int __result; \
+ do __result = (long int) (expression); \
+ while (__result == -1L && errno == EINTR); \
+ __result; }))
+#endif
+
+#ifndef HAVE_DOSISH_SYSTEM
+static int uid_set = 0;
+static uid_t real_uid, file_uid;
+#endif /*!HAVE_DOSISH_SYSTEM*/
+
+/* write DATA of size BYTES to FD, until all is written or an error occurs */
+ssize_t xwrite(int fd, const void *data, size_t bytes)
+{
+ char *ptr;
+ size_t todo;
+ ssize_t written = 0;
+
+ for (ptr = (char *)data, todo = bytes; todo; ptr += written, todo -= written)
+ if ((written = TEMP_FAILURE_RETRY(write(fd, ptr, todo))) < 0)
+ break;
+ return written;
+}
+
+#if 0
+extern int debug;
+
+int debugmsg(const char *fmt, ...)
+{
+ va_list va;
+ int ret;
+
+ if (debug) {
+ va_start(va, fmt);
+ fprintf(stderr, "\e[4m");
+ ret = vfprintf(stderr, fmt, va);
+ fprintf(stderr, "\e[24m");
+ va_end(va);
+ return ret;
+ } else
+ return 0;
+}
+#endif
+
+/* initialize uid variables */
+#ifndef HAVE_DOSISH_SYSTEM
+static void init_uids(void)
+{
+ real_uid = getuid();
+ file_uid = geteuid();
+ uid_set = 1;
+}
+#endif
+
+
+#if 0 /* Not used. */
+/* lower privileges to the real user's */
+void lower_privs()
+{
+ if (!uid_set)
+ init_uids();
+ if (real_uid != file_uid) {
+#ifdef HAVE_SETEUID
+ if (seteuid(real_uid) < 0) {
+ perror("lowering privileges failed");
+ exit(EXIT_FAILURE);
+ }
+#else
+ fprintf(stderr, _("Warning: running q-agent setuid on this system is dangerous\n"));
+#endif /* HAVE_SETEUID */
+ }
+}
+#endif /* if 0 */
+
+#if 0 /* Not used. */
+/* raise privileges to the effective user's */
+void raise_privs()
+{
+ assert(real_uid >= 0); /* lower_privs() must be called before this */
+#ifdef HAVE_SETEUID
+ if (real_uid != file_uid && seteuid(file_uid) < 0) {
+ perror("Warning: raising privileges failed");
+ }
+#endif /* HAVE_SETEUID */
+}
+#endif /* if 0 */
+
+/* drop all additional privileges */
+void drop_privs()
+{
+#ifndef HAVE_DOSISH_SYSTEM
+ if (!uid_set)
+ init_uids();
+ if (real_uid != file_uid) {
+ if (setuid(real_uid) < 0) {
+ perror("dropping privileges failed");
+ exit(EXIT_FAILURE);
+ }
+ file_uid = real_uid;
+ }
+#endif
+}
diff --git a/secmem/util.h b/secmem/util.h
new file mode 100644
index 0000000..7986c99
--- /dev/null
+++ b/secmem/util.h
@@ -0,0 +1,66 @@
+/* Quintuple Agent utilities
+ * Copyright (C) 1999 Robert Bihlmeyer <robbe@orcus.priv.at>
+ * Copyright (C) 2003 g10 Code GmbH
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef _UTIL_H
+#define _UTIL_H
+
+#include <sys/types.h>
+
+#ifndef HAVE_BYTE_TYPEDEF
+# undef byte
+# ifdef __riscos__
+ /* Norcroft treats char == unsigned char but char* != unsigned char* */
+ typedef char byte;
+# else
+ typedef unsigned char byte;
+# endif
+# define HAVE_BYTE_TYPEDEF
+#endif
+
+#ifndef HAVE_ULONG_TYPEDEF
+# undef ulong
+ typedef unsigned long ulong;
+# define HAVE_ULONG_TYPEDEF
+#endif
+
+
+ssize_t xwrite(int, const void *, size_t); /* write until finished */
+int debugmsg(const char *, ...); /* output a debug message if debugging==on */
+void drop_privs(void); /* finally drop privileges */
+
+
+/* To avoid that a compiler optimizes certain memset calls away, these
+ macros may be used instead. */
+#define wipememory2(_ptr,_set,_len) do { \
+ volatile char *_vptr=(volatile char *)(_ptr); \
+ size_t _vlen=(_len); \
+ while(_vlen) { *_vptr=(_set); _vptr++; _vlen--; } \
+ } while(0)
+#define wipememory(_ptr,_len) wipememory2(_ptr,0,_len)
+#define wipe(_ptr,_len) wipememory2(_ptr,0,_len)
+
+
+
+
+#define xtoi_1(p) (*(p) <= '9'? (*(p)- '0'): \
+ *(p) <= 'F'? (*(p)-'A'+10):(*(p)-'a'+10))
+#define xtoi_2(p) ((xtoi_1(p) * 16) + xtoi_1((p)+1))
+
+
+#endif
bgstack15