- Added support for CONFIG_PPPOL2TP by sigwall <fionov@gmail.com> - Require current ppp because some old versions lacked pppol2tp.so pluginepel9
parent
bd5a80d261
commit
8ecc1e5656
@ -0,0 +1,436 @@
|
|||||||
|
diff --git a/Makefile b/Makefile
|
||||||
|
index 6f6481f..778f38d 100644
|
||||||
|
--- a/Makefile
|
||||||
|
+++ b/Makefile
|
||||||
|
@@ -62,8 +62,8 @@
|
||||||
|
# are packages seperately (eg kernel-headers on Fedora)
|
||||||
|
# Note: 2.6.23+ support still needs some changes in the xl2tpd source
|
||||||
|
#
|
||||||
|
-#OSFLAGS+= -DUSE_KERNEL
|
||||||
|
-#
|
||||||
|
+# Kernel mode fixed by sigwall <fionov@gmail.com>
|
||||||
|
+OSFLAGS+= -DUSE_KERNEL
|
||||||
|
#
|
||||||
|
# Uncomment the next line for FreeBSD
|
||||||
|
#
|
||||||
|
diff --git a/call.c b/call.c
|
||||||
|
index d1b1858..b672f91 100644
|
||||||
|
--- a/call.c
|
||||||
|
+++ b/call.c
|
||||||
|
@@ -680,6 +680,8 @@ struct call *get_call (int tunnel, int call, struct in_addr addr, int port,
|
||||||
|
st->peer.sin_port = port;
|
||||||
|
st->refme = refme;
|
||||||
|
st->refhim = refhim;
|
||||||
|
+ st->udp_fd = -1;
|
||||||
|
+ st->pppox_fd = -1;
|
||||||
|
bcopy (&addr, &st->peer.sin_addr, sizeof (addr));
|
||||||
|
st->next = tunnels.head;
|
||||||
|
tunnels.head = st;
|
||||||
|
diff --git a/control.c b/control.c
|
||||||
|
index 0892df9..9362ffd 100644
|
||||||
|
--- a/control.c
|
||||||
|
+++ b/control.c
|
||||||
|
@@ -596,6 +596,9 @@ int control_finish (struct tunnel *t, struct call *c)
|
||||||
|
if (gconfig.debug_state)
|
||||||
|
l2tp_log (LOG_DEBUG, "%s: sending SCCCN\n", __FUNCTION__);
|
||||||
|
control_xmit (buf);
|
||||||
|
+
|
||||||
|
+ connect_pppol2tp(t);
|
||||||
|
+
|
||||||
|
/* Schedule a HELLO */
|
||||||
|
tv.tv_sec = HELLO_DELAY;
|
||||||
|
tv.tv_usec = 0;
|
||||||
|
@@ -608,6 +611,7 @@ int control_finish (struct tunnel *t, struct call *c)
|
||||||
|
"Connection established to %s, %d. Local: %d, Remote: %d (ref=%u/%u).\n",
|
||||||
|
IPADDY (t->peer.sin_addr),
|
||||||
|
ntohs (t->peer.sin_port), t->ourtid, t->tid, t->refme, t->refhim);
|
||||||
|
+
|
||||||
|
if (t->lac)
|
||||||
|
{
|
||||||
|
/* This is part of a LAC, so we want to go ahead
|
||||||
|
@@ -635,6 +639,9 @@ int control_finish (struct tunnel *t, struct call *c)
|
||||||
|
IPADDY (t->peer.sin_addr),
|
||||||
|
ntohs (t->peer.sin_port), t->ourtid, t->tid, t->refme, t->refhim,
|
||||||
|
t->lns->entname);
|
||||||
|
+
|
||||||
|
+ connect_pppol2tp(t);
|
||||||
|
+
|
||||||
|
/* Schedule a HELLO */
|
||||||
|
tv.tv_sec = HELLO_DELAY;
|
||||||
|
tv.tv_usec = 0;
|
||||||
|
diff --git a/l2tp.h b/l2tp.h
|
||||||
|
index 2724fff..856423f 100644
|
||||||
|
--- a/l2tp.h
|
||||||
|
+++ b/l2tp.h
|
||||||
|
@@ -167,6 +167,8 @@ struct tunnel
|
||||||
|
int ourrws; /* Receive Window Size */
|
||||||
|
int rxspeed; /* Receive bps */
|
||||||
|
int txspeed; /* Transmit bps */
|
||||||
|
+ int udp_fd; /* UDP fd */
|
||||||
|
+ int pppox_fd; /* PPPOX tunnel fd */
|
||||||
|
struct call *self;
|
||||||
|
struct lns *lns; /* LNS that owns us */
|
||||||
|
struct lac *lac; /* LAC that owns us */
|
||||||
|
@@ -220,6 +222,7 @@ extern void control_xmit (void *);
|
||||||
|
extern int ppd;
|
||||||
|
extern int switch_io; /* jz */
|
||||||
|
extern int control_fd;
|
||||||
|
+extern int connect_pppol2tp(struct tunnel *t);
|
||||||
|
extern int start_pppd (struct call *c, struct ppp_opts *);
|
||||||
|
extern void magic_lac_dial (void *);
|
||||||
|
extern int get_entropy (unsigned char *, int);
|
||||||
|
diff --git a/linux/include/linux/if_pppol2tp.h b/linux/include/linux/if_pppol2tp.h
|
||||||
|
index a7d6a22..0795e4a 100644
|
||||||
|
--- a/linux/include/linux/if_pppol2tp.h
|
||||||
|
+++ b/linux/include/linux/if_pppol2tp.h
|
||||||
|
@@ -36,6 +36,20 @@ struct pppol2tp_addr
|
||||||
|
__u16 d_tunnel, d_session; /* For sending outgoing packets */
|
||||||
|
};
|
||||||
|
|
||||||
|
+/* The L2TPv3 protocol changes tunnel and session ids from 16 to 32
|
||||||
|
+ * bits. So we need a different sockaddr structure.
|
||||||
|
+ */
|
||||||
|
+struct pppol2tpv3_addr {
|
||||||
|
+ pid_t pid; /* pid that owns the fd.
|
||||||
|
+ * 0 => current */
|
||||||
|
+ int fd; /* FD of UDP or IP socket to use */
|
||||||
|
+
|
||||||
|
+ struct sockaddr_in addr; /* IP address and port to send to */
|
||||||
|
+
|
||||||
|
+ __u32 s_tunnel, s_session; /* For matching incoming packets */
|
||||||
|
+ __u32 d_tunnel, d_session; /* For sending outgoing packets */
|
||||||
|
+};
|
||||||
|
+
|
||||||
|
/* Socket options:
|
||||||
|
* DEBUG - bitmask of debug message categories
|
||||||
|
* SENDSEQ - 0 => don't send packets with sequence numbers
|
||||||
|
diff --git a/network.c b/network.c
|
||||||
|
index 241bd82..fde250e 100644
|
||||||
|
--- a/network.c
|
||||||
|
+++ b/network.c
|
||||||
|
@@ -22,6 +22,7 @@
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
+#include <sys/wait.h>
|
||||||
|
#ifndef LINUX
|
||||||
|
# include <sys/uio.h>
|
||||||
|
#endif
|
||||||
|
@@ -36,6 +37,51 @@ int server_socket; /* Server socket */
|
||||||
|
int kernel_support; /* Kernel Support there or not? */
|
||||||
|
#endif
|
||||||
|
|
||||||
|
+#ifdef USE_KERNEL
|
||||||
|
+void modprobe() {
|
||||||
|
+ char * modules[] = { "l2tp_ppp", "pppol2tp", NULL };
|
||||||
|
+ char ** module;
|
||||||
|
+ char buf[256], *tok;
|
||||||
|
+ int pid, exit_status, fd;
|
||||||
|
+
|
||||||
|
+ FILE * fmod = fopen("/proc/modules", "r");
|
||||||
|
+
|
||||||
|
+ if (fmod == NULL)
|
||||||
|
+ return;
|
||||||
|
+
|
||||||
|
+ while (fgets(buf, 255, fmod) != NULL) {
|
||||||
|
+ if ((tok = strtok(buf, " ")) != NULL) {
|
||||||
|
+ for (module = modules; *module != NULL; ++module) {
|
||||||
|
+ if (!strcmp(*module, tok)) {
|
||||||
|
+ fclose(fmod);
|
||||||
|
+ return;
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ fclose(fmod);
|
||||||
|
+
|
||||||
|
+ for (module = modules; *module != NULL; ++module) {
|
||||||
|
+ if ((pid = fork()) >= 0) {
|
||||||
|
+ if (pid == 0) {
|
||||||
|
+ setenv("PATH", "/sbin:/usr/sbin:/bin:/usr/bin", 1);
|
||||||
|
+ if ((fd = open("/dev/null", O_RDWR)) > -1) {
|
||||||
|
+ dup2(fd, 1);
|
||||||
|
+ dup2(fd, 2);
|
||||||
|
+ }
|
||||||
|
+ execlp("modprobe", "modprobe", "-q", *module, (char *)NULL);
|
||||||
|
+ exit(1);
|
||||||
|
+ } else {
|
||||||
|
+ if ((pid = waitpid(pid, &exit_status, 0)) != -1 && WIFEXITED(exit_status)) {
|
||||||
|
+ if (WEXITSTATUS(exit_status) == 0)
|
||||||
|
+ return;
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+#endif
|
||||||
|
|
||||||
|
int init_network (void)
|
||||||
|
{
|
||||||
|
@@ -45,6 +91,7 @@ int init_network (void)
|
||||||
|
server.sin_family = AF_INET;
|
||||||
|
server.sin_addr.s_addr = gconfig.listenaddr;
|
||||||
|
server.sin_port = htons (gconfig.port);
|
||||||
|
+ int flags;
|
||||||
|
if ((server_socket = socket (PF_INET, SOCK_DGRAM, 0)) < 0)
|
||||||
|
{
|
||||||
|
l2tp_log (LOG_CRIT, "%s: Unable to allocate socket. Terminating.\n",
|
||||||
|
@@ -52,6 +99,10 @@ int init_network (void)
|
||||||
|
return -EINVAL;
|
||||||
|
};
|
||||||
|
|
||||||
|
+ flags = 1;
|
||||||
|
+ setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &flags, sizeof(flags));
|
||||||
|
+ setsockopt(server_socket, SOL_SOCKET, SO_NO_CHECK, &flags, sizeof(flags));
|
||||||
|
+
|
||||||
|
if (bind (server_socket, (struct sockaddr *) &server, sizeof (server)))
|
||||||
|
{
|
||||||
|
close (server_socket);
|
||||||
|
@@ -91,6 +142,7 @@ int init_network (void)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
+ modprobe();
|
||||||
|
int kernel_fd = socket(AF_PPPOX, SOCK_DGRAM, PX_PROTO_OL2TP);
|
||||||
|
if (kernel_fd < 0)
|
||||||
|
{
|
||||||
|
@@ -321,6 +373,11 @@ int build_fdset (fd_set *readfds)
|
||||||
|
|
||||||
|
while (tun)
|
||||||
|
{
|
||||||
|
+ if (tun->udp_fd > -1) {
|
||||||
|
+ if (tun->udp_fd > max)
|
||||||
|
+ max = tun->udp_fd;
|
||||||
|
+ FD_SET (tun->udp_fd, readfds);
|
||||||
|
+ }
|
||||||
|
call = tun->call_head;
|
||||||
|
while (call)
|
||||||
|
{
|
||||||
|
@@ -390,6 +447,8 @@ void network_thread ()
|
||||||
|
struct iovec iov;
|
||||||
|
char cbuf[256];
|
||||||
|
unsigned int refme, refhim;
|
||||||
|
+ int * currentfd;
|
||||||
|
+ int server_socket_processed;
|
||||||
|
|
||||||
|
/* This one buffer can be recycled for everything except control packets */
|
||||||
|
buf = new_buf (MAX_RECV_SIZE);
|
||||||
|
@@ -428,7 +487,21 @@ void network_thread ()
|
||||||
|
{
|
||||||
|
do_control ();
|
||||||
|
}
|
||||||
|
- if (FD_ISSET (server_socket, &readfds))
|
||||||
|
+ server_socket_processed = 0;
|
||||||
|
+ currentfd = NULL;
|
||||||
|
+ st = tunnels.head;
|
||||||
|
+ while (st || !server_socket_processed) {
|
||||||
|
+ if (st && (st->udp_fd == -1)) {
|
||||||
|
+ st=st->next;
|
||||||
|
+ continue;
|
||||||
|
+ }
|
||||||
|
+ if (st) {
|
||||||
|
+ currentfd = &st->udp_fd;
|
||||||
|
+ } else {
|
||||||
|
+ currentfd = &server_socket;
|
||||||
|
+ server_socket_processed = 1;
|
||||||
|
+ }
|
||||||
|
+ if (FD_ISSET (*currentfd, &readfds))
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Okay, now we're ready for reading and processing new data.
|
||||||
|
@@ -457,12 +530,19 @@ void network_thread ()
|
||||||
|
msgh.msg_flags = 0;
|
||||||
|
|
||||||
|
/* Receive one packet. */
|
||||||
|
- recvsize = recvmsg(server_socket, &msgh, 0);
|
||||||
|
+ recvsize = recvmsg(*currentfd, &msgh, 0);
|
||||||
|
|
||||||
|
if (recvsize < MIN_PAYLOAD_HDR_LEN)
|
||||||
|
{
|
||||||
|
if (recvsize < 0)
|
||||||
|
{
|
||||||
|
+ if (errno == ECONNREFUSED) {
|
||||||
|
+ close(*currentfd);
|
||||||
|
+ }
|
||||||
|
+ if ((errno == ECONNREFUSED) ||
|
||||||
|
+ (errno == EBADF)) {
|
||||||
|
+ *currentfd = -1;
|
||||||
|
+ }
|
||||||
|
if (errno != EAGAIN)
|
||||||
|
l2tp_log (LOG_WARNING,
|
||||||
|
"%s: recvfrom returned error %d (%s)\n",
|
||||||
|
@@ -567,6 +647,8 @@ void network_thread ()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
+ if (st) st=st->next;
|
||||||
|
+ }
|
||||||
|
|
||||||
|
/*
|
||||||
|
* finished obvious sources, look for data from PPP connections.
|
||||||
|
@@ -639,3 +721,82 @@ void network_thread ()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
+
|
||||||
|
+int connect_pppol2tp(struct tunnel *t) {
|
||||||
|
+#ifdef USE_KERNEL
|
||||||
|
+ if (kernel_support) {
|
||||||
|
+ int ufd = -1, fd2 = -1;
|
||||||
|
+ int flags;
|
||||||
|
+ struct sockaddr_pppol2tp sax;
|
||||||
|
+
|
||||||
|
+ struct sockaddr_in server;
|
||||||
|
+ server.sin_family = AF_INET;
|
||||||
|
+ server.sin_addr.s_addr = gconfig.listenaddr;
|
||||||
|
+ server.sin_port = htons (gconfig.port);
|
||||||
|
+ if ((ufd = socket (PF_INET, SOCK_DGRAM, 0)) < 0)
|
||||||
|
+ {
|
||||||
|
+ l2tp_log (LOG_CRIT, "%s: Unable to allocate UDP socket. Terminating.\n",
|
||||||
|
+ __FUNCTION__);
|
||||||
|
+ return -EINVAL;
|
||||||
|
+ };
|
||||||
|
+
|
||||||
|
+ flags=1;
|
||||||
|
+ setsockopt(ufd, SOL_SOCKET, SO_REUSEADDR, &flags, sizeof(flags));
|
||||||
|
+ setsockopt(ufd, SOL_SOCKET, SO_NO_CHECK, &flags, sizeof(flags));
|
||||||
|
+
|
||||||
|
+ if (bind (ufd, (struct sockaddr *) &server, sizeof (server)))
|
||||||
|
+ {
|
||||||
|
+ close (ufd);
|
||||||
|
+ l2tp_log (LOG_CRIT, "%s: Unable to bind UDP socket: %s. Terminating.\n",
|
||||||
|
+ __FUNCTION__, strerror(errno), errno);
|
||||||
|
+ return -EINVAL;
|
||||||
|
+ };
|
||||||
|
+ server = t->peer;
|
||||||
|
+ flags = fcntl(ufd, F_GETFL);
|
||||||
|
+ if (flags == -1 || fcntl(ufd, F_SETFL, flags | O_NONBLOCK) == -1) {
|
||||||
|
+ l2tp_log (LOG_WARNING, "%s: Unable to set UDP socket nonblock.\n",
|
||||||
|
+ __FUNCTION__);
|
||||||
|
+ return -EINVAL;
|
||||||
|
+ }
|
||||||
|
+ if (connect (ufd, (struct sockaddr *) &server, sizeof(server)) < 0) {
|
||||||
|
+ l2tp_log (LOG_CRIT, "%s: Unable to connect UDP peer. Terminating.\n",
|
||||||
|
+ __FUNCTION__);
|
||||||
|
+ return -EINVAL;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ t->udp_fd=ufd;
|
||||||
|
+
|
||||||
|
+ fd2 = socket(AF_PPPOX, SOCK_DGRAM, PX_PROTO_OL2TP);
|
||||||
|
+ if (fd2 < 0) {
|
||||||
|
+ l2tp_log (LOG_WARNING, "%s: Unable to allocate PPPoL2TP socket.\n",
|
||||||
|
+ __FUNCTION__);
|
||||||
|
+ return -EINVAL;
|
||||||
|
+ }
|
||||||
|
+ flags = fcntl(fd2, F_GETFL);
|
||||||
|
+ if (flags == -1 || fcntl(fd2, F_SETFL, flags | O_NONBLOCK) == -1) {
|
||||||
|
+ l2tp_log (LOG_WARNING, "%s: Unable to set PPPoL2TP socket nonblock.\n",
|
||||||
|
+ __FUNCTION__);
|
||||||
|
+ return -EINVAL;
|
||||||
|
+ }
|
||||||
|
+ sax.sa_family = AF_PPPOX;
|
||||||
|
+ sax.sa_protocol = PX_PROTO_OL2TP;
|
||||||
|
+ sax.pppol2tp.pid = 0;
|
||||||
|
+ sax.pppol2tp.fd = t->udp_fd;
|
||||||
|
+ sax.pppol2tp.addr.sin_addr.s_addr = t->peer.sin_addr.s_addr;
|
||||||
|
+ sax.pppol2tp.addr.sin_port = t->peer.sin_port;
|
||||||
|
+ sax.pppol2tp.addr.sin_family = AF_INET;
|
||||||
|
+ sax.pppol2tp.s_tunnel = t->ourtid;
|
||||||
|
+ sax.pppol2tp.s_session = 0;
|
||||||
|
+ sax.pppol2tp.d_tunnel = t->tid;
|
||||||
|
+ sax.pppol2tp.d_session = 0;
|
||||||
|
+ if ((connect(fd2, (struct sockaddr *)&sax, sizeof(sax))) < 0) {
|
||||||
|
+ l2tp_log (LOG_WARNING, "%s: Unable to connect PPPoL2TP socket. %d %s\n",
|
||||||
|
+ __FUNCTION__, errno, strerror(errno));
|
||||||
|
+ close(fd2);
|
||||||
|
+ return -EINVAL;
|
||||||
|
+ }
|
||||||
|
+ t->pppox_fd = fd2;
|
||||||
|
+ }
|
||||||
|
+#endif
|
||||||
|
+ return 0;
|
||||||
|
+}
|
||||||
|
diff --git a/xl2tpd.c b/xl2tpd.c
|
||||||
|
index 307ac2e..3fb6dd7 100644
|
||||||
|
--- a/xl2tpd.c
|
||||||
|
+++ b/xl2tpd.c
|
||||||
|
@@ -278,7 +278,11 @@ void death_handler (int signal)
|
||||||
|
struct tunnel *st, *st2;
|
||||||
|
int sec;
|
||||||
|
l2tp_log (LOG_CRIT, "%s: Fatal signal %d received\n", __FUNCTION__, signal);
|
||||||
|
+#ifdef USE_KERNEL
|
||||||
|
+ if (kernel_support || signal != SIGTERM) {
|
||||||
|
+#else
|
||||||
|
if (signal != SIGTERM) {
|
||||||
|
+#endif
|
||||||
|
st = tunnels.head;
|
||||||
|
while (st)
|
||||||
|
{
|
||||||
|
@@ -349,7 +353,7 @@ int start_pppd (struct call *c, struct ppp_opts *opts)
|
||||||
|
int flags;
|
||||||
|
#endif
|
||||||
|
int pos = 1;
|
||||||
|
- int fd2;
|
||||||
|
+ int fd2 = -1;
|
||||||
|
#ifdef DEBUG_PPPD
|
||||||
|
int x;
|
||||||
|
#endif
|
||||||
|
@@ -397,7 +401,7 @@ int start_pppd (struct call *c, struct ppp_opts *opts)
|
||||||
|
sax.sa_family = AF_PPPOX;
|
||||||
|
sax.sa_protocol = PX_PROTO_OL2TP;
|
||||||
|
sax.pppol2tp.pid = 0;
|
||||||
|
- sax.pppol2tp.fd = server_socket;
|
||||||
|
+ sax.pppol2tp.fd = c->container->udp_fd;
|
||||||
|
sax.pppol2tp.addr.sin_addr.s_addr = c->container->peer.sin_addr.s_addr;
|
||||||
|
sax.pppol2tp.addr.sin_port = c->container->peer.sin_port;
|
||||||
|
sax.pppol2tp.addr.sin_family = AF_INET;
|
||||||
|
@@ -408,6 +412,7 @@ int start_pppd (struct call *c, struct ppp_opts *opts)
|
||||||
|
if (connect(fd2, (struct sockaddr *)&sax, sizeof(sax)) < 0) {
|
||||||
|
l2tp_log (LOG_WARNING, "%s: Unable to connect PPPoL2TP socket.\n",
|
||||||
|
__FUNCTION__);
|
||||||
|
+ close(fd2);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
stropt[pos++] = strdup ("plugin");
|
||||||
|
@@ -484,7 +489,7 @@ int start_pppd (struct call *c, struct ppp_opts *opts)
|
||||||
|
dup2 (fd2, 0);
|
||||||
|
dup2 (fd2, 1);
|
||||||
|
close(fd2);
|
||||||
|
-
|
||||||
|
+ }
|
||||||
|
/* close all the calls pty fds */
|
||||||
|
st = tunnels.head;
|
||||||
|
while (st)
|
||||||
|
@@ -492,12 +497,17 @@ int start_pppd (struct call *c, struct ppp_opts *opts)
|
||||||
|
sc = st->call_head;
|
||||||
|
while (sc)
|
||||||
|
{
|
||||||
|
- close (sc->fd);
|
||||||
|
+#ifdef USE_KERNEL
|
||||||
|
+ if (kernel_support) {
|
||||||
|
+ close(st->udp_fd); /* tunnel UDP fd */
|
||||||
|
+ close(st->pppox_fd); /* tunnel PPPoX fd */
|
||||||
|
+ } else
|
||||||
|
+#endif
|
||||||
|
+ close (sc->fd); /* call pty fd */
|
||||||
|
sc = sc->next;
|
||||||
|
}
|
||||||
|
st = st->next;
|
||||||
|
}
|
||||||
|
- }
|
||||||
|
|
||||||
|
/* close the UDP socket fd */
|
||||||
|
close (server_socket);
|
||||||
|
@@ -615,6 +625,10 @@ void destroy_tunnel (struct tunnel *t)
|
||||||
|
the memory pointed to by t->chal_us.vector at some other place */
|
||||||
|
if (t->chal_them.vector)
|
||||||
|
free (t->chal_them.vector);
|
||||||
|
+ if (t->pppox_fd > -1 )
|
||||||
|
+ close (t->pppox_fd);
|
||||||
|
+ if (t->udp_fd > -1 )
|
||||||
|
+ close (t->udp_fd);
|
||||||
|
free (t);
|
||||||
|
free (me);
|
||||||
|
}
|
Loading…
Reference in new issue