2010-11-12 14:32:18 +05:30
|
|
|
/* linux.c - ifchd Linux-specific functions
|
|
|
|
*
|
2013-05-08 15:57:22 +05:30
|
|
|
* Copyright (c) 2004-2013 Nicholas J. Kain <njkain at gmail dot com>
|
2011-07-25 12:00:57 +05:30
|
|
|
* All rights reserved.
|
2010-11-12 14:32:18 +05:30
|
|
|
*
|
2011-07-25 12:00:57 +05:30
|
|
|
* Redistribution and use in source and binary forms, with or without
|
|
|
|
* modification, are permitted provided that the following conditions are met:
|
2010-11-12 14:32:18 +05:30
|
|
|
*
|
2011-07-25 12:00:57 +05:30
|
|
|
* - Redistributions of source code must retain the above copyright notice,
|
|
|
|
* this list of conditions and the following disclaimer.
|
2010-11-12 14:32:18 +05:30
|
|
|
*
|
2011-07-25 12:00:57 +05:30
|
|
|
* - 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.
|
|
|
|
*
|
|
|
|
* 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 HOLDER 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.
|
2010-11-12 14:32:18 +05:30
|
|
|
*/
|
|
|
|
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#define __USE_GNU 1
|
|
|
|
#include <sys/socket.h>
|
|
|
|
#include <sys/un.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/ioctl.h>
|
|
|
|
#include <netinet/in.h>
|
|
|
|
#include <arpa/inet.h>
|
|
|
|
#include <net/route.h>
|
|
|
|
#include <net/if.h>
|
|
|
|
#include <netpacket/packet.h>
|
|
|
|
#include <pwd.h>
|
|
|
|
#include <grp.h>
|
|
|
|
|
|
|
|
#include <errno.h>
|
|
|
|
|
2010-11-13 05:14:49 +05:30
|
|
|
#include "ifchd-defines.h"
|
2010-11-12 14:32:18 +05:30
|
|
|
#include "log.h"
|
2011-07-25 11:31:38 +05:30
|
|
|
#include "ifch_proto.h"
|
2010-11-12 16:12:07 +05:30
|
|
|
#include "strl.h"
|
2010-11-12 14:32:18 +05:30
|
|
|
|
2012-07-21 00:26:17 +05:30
|
|
|
extern struct ifchd_client clients[SOCK_QUEUE];
|
2012-07-21 00:47:44 +05:30
|
|
|
|
|
|
|
static size_t numokif;
|
|
|
|
static char okif[MAX_IFACES][IFNAMSIZ];
|
2010-11-12 14:32:18 +05:30
|
|
|
|
|
|
|
/* Adds to the list of interface names ifchd clients are allowed to change. */
|
|
|
|
void add_permitted_if(char *s)
|
|
|
|
{
|
2012-07-21 00:47:44 +05:30
|
|
|
if (numokif >= MAX_IFACES)
|
2010-11-13 01:03:17 +05:30
|
|
|
return;
|
2013-05-06 16:37:54 +05:30
|
|
|
strnkcpy(okif[numokif++], s, IFNAMSIZ);
|
2010-11-12 14:32:18 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
/* Checks if changes are permitted to a given interface. 1 == allowed */
|
|
|
|
static int is_permitted(char *name)
|
|
|
|
{
|
|
|
|
/* If empty, permit all. */
|
2012-07-21 00:47:44 +05:30
|
|
|
if (!numokif)
|
2010-11-13 01:03:17 +05:30
|
|
|
return 1;
|
2010-11-12 14:32:18 +05:30
|
|
|
|
|
|
|
if (!name || strlen(name) == 0)
|
2010-11-13 01:03:17 +05:30
|
|
|
return 0;
|
2012-07-21 00:47:44 +05:30
|
|
|
for (size_t i = 0; i < numokif; ++i) {
|
|
|
|
if (strcmp(name, okif[i]) == 0)
|
2010-11-13 01:03:17 +05:30
|
|
|
return 1;
|
2010-11-12 14:32:18 +05:30
|
|
|
}
|
2013-05-08 15:57:22 +05:30
|
|
|
log_line("attempt to modify interface %s denied", name);
|
2010-11-12 14:32:18 +05:30
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Verify that peer is authorized to connect (return 1 on success). */
|
|
|
|
int authorized_peer(int sk, pid_t pid, uid_t uid, gid_t gid)
|
|
|
|
{
|
|
|
|
int ret = 0;
|
|
|
|
unsigned int cl;
|
|
|
|
struct ucred cr;
|
|
|
|
|
|
|
|
/* No credentials to verify. */
|
|
|
|
if ( !(pid || uid || gid) )
|
2010-11-13 01:03:17 +05:30
|
|
|
return 1;
|
2010-11-12 14:32:18 +05:30
|
|
|
|
|
|
|
/* Verify that peer has authorized uid/gid/pid. */
|
|
|
|
cl = sizeof(struct ucred);
|
|
|
|
if (getsockopt(sk, SOL_SOCKET, SO_PEERCRED, &cr, &cl) != -1) {
|
2010-11-13 01:03:17 +05:30
|
|
|
if ((pid == 0 || cr.pid == pid) ||
|
|
|
|
(uid == 0 || cr.uid == uid) ||
|
|
|
|
(gid == 0 || cr.gid == gid))
|
|
|
|
ret = 1;
|
2010-11-12 14:32:18 +05:30
|
|
|
} else
|
2013-05-08 15:57:22 +05:30
|
|
|
log_line("getsockopt returned an error: %s", strerror(errno));
|
2010-11-12 14:32:18 +05:30
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2013-05-08 15:57:22 +05:30
|
|
|
void perform_interface(struct ifchd_client *cl, const char *str, size_t len)
|
2010-11-12 14:32:18 +05:30
|
|
|
{
|
|
|
|
if (!str)
|
2010-11-13 01:03:17 +05:30
|
|
|
return;
|
2010-11-12 14:32:18 +05:30
|
|
|
|
|
|
|
/* Update interface name. */
|
2012-07-21 03:01:15 +05:30
|
|
|
memset(cl->ifnam, '\0', IFNAMSIZ);
|
2013-05-06 16:37:54 +05:30
|
|
|
strnkcpy(cl->ifnam, str, IFNAMSIZ);
|
2010-11-12 14:32:18 +05:30
|
|
|
}
|
|
|
|
|
2012-07-21 03:01:15 +05:30
|
|
|
static int set_if_flag(struct ifchd_client *cl, short flag)
|
2010-11-12 14:32:18 +05:30
|
|
|
{
|
|
|
|
int fd, ret = -1;
|
|
|
|
struct ifreq ifrt;
|
|
|
|
|
2012-07-21 03:01:15 +05:30
|
|
|
if (!is_permitted(cl->ifnam))
|
2010-11-13 01:03:17 +05:30
|
|
|
goto out0;
|
2010-11-12 14:32:18 +05:30
|
|
|
|
2011-06-25 20:41:48 +05:30
|
|
|
fd = socket(AF_INET, SOCK_DGRAM, 0);
|
2010-11-12 14:32:18 +05:30
|
|
|
if (fd == -1) {
|
2013-05-08 15:57:22 +05:30
|
|
|
log_line("%s: (set_if_flag) failed to open interface socket: %s",
|
2012-07-21 03:01:15 +05:30
|
|
|
cl->ifnam, strerror(errno));
|
2010-11-13 01:03:17 +05:30
|
|
|
goto out0;
|
2010-11-12 14:32:18 +05:30
|
|
|
}
|
|
|
|
|
2013-05-06 16:37:54 +05:30
|
|
|
strnkcpy(ifrt.ifr_name, cl->ifnam, IFNAMSIZ);
|
2010-11-12 14:32:18 +05:30
|
|
|
if (ioctl(fd, SIOCGIFFLAGS, &ifrt) < 0) {
|
2013-05-08 15:57:22 +05:30
|
|
|
log_line("%s: unknown interface: %s", cl->ifnam, strerror(errno));
|
2010-11-13 01:03:17 +05:30
|
|
|
goto out1;
|
2010-11-12 14:32:18 +05:30
|
|
|
}
|
2011-04-30 17:00:07 +05:30
|
|
|
if (((ifrt.ifr_flags & flag ) ^ flag) & flag) {
|
2013-05-06 16:37:54 +05:30
|
|
|
strnkcpy(ifrt.ifr_name, cl->ifnam, IFNAMSIZ);
|
2011-04-30 17:00:07 +05:30
|
|
|
ifrt.ifr_flags |= flag;
|
|
|
|
if (ioctl(fd, SIOCSIFFLAGS, &ifrt) < 0) {
|
2013-05-08 15:57:22 +05:30
|
|
|
log_line("%s: failed to set interface flags: %s",
|
2012-07-21 03:01:15 +05:30
|
|
|
cl->ifnam, strerror(errno));
|
2011-04-30 17:00:07 +05:30
|
|
|
goto out1;
|
|
|
|
}
|
|
|
|
} else
|
|
|
|
ret = 0;
|
2010-11-12 14:32:18 +05:30
|
|
|
|
2010-11-13 01:03:17 +05:30
|
|
|
out1:
|
2010-11-12 14:32:18 +05:30
|
|
|
close(fd);
|
2010-11-13 01:03:17 +05:30
|
|
|
out0:
|
2010-11-12 14:32:18 +05:30
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Sets IP address on an interface and brings it up. */
|
2013-05-08 15:57:22 +05:30
|
|
|
void perform_ip(struct ifchd_client *cl, const char *str, size_t len)
|
2010-11-12 14:32:18 +05:30
|
|
|
{
|
|
|
|
int fd;
|
|
|
|
struct in_addr ipaddr;
|
|
|
|
struct ifreq ifrt;
|
|
|
|
struct sockaddr_in sin;
|
|
|
|
|
|
|
|
if (!str)
|
2010-11-13 01:03:17 +05:30
|
|
|
return;
|
2012-07-21 03:01:15 +05:30
|
|
|
if (!is_permitted(cl->ifnam))
|
2010-11-13 01:03:17 +05:30
|
|
|
return;
|
2011-07-12 02:41:19 +05:30
|
|
|
if (!inet_pton(AF_INET, str, &ipaddr))
|
2010-11-13 01:03:17 +05:30
|
|
|
return;
|
2012-07-21 03:01:15 +05:30
|
|
|
if (set_if_flag(cl, (IFF_UP | IFF_RUNNING)))
|
2010-11-13 01:03:17 +05:30
|
|
|
return;
|
2010-11-12 14:32:18 +05:30
|
|
|
|
2013-05-06 16:37:54 +05:30
|
|
|
strnkcpy(ifrt.ifr_name, cl->ifnam, IFNAMSIZ);
|
2010-11-12 14:32:18 +05:30
|
|
|
memset(&sin, 0, sizeof(struct sockaddr));
|
|
|
|
sin.sin_family = AF_INET;
|
|
|
|
sin.sin_addr = ipaddr;
|
|
|
|
memcpy(&ifrt.ifr_addr, &sin, sizeof(struct sockaddr));
|
|
|
|
|
2011-06-25 20:41:48 +05:30
|
|
|
fd = socket(AF_INET, SOCK_DGRAM, 0);
|
2010-11-12 14:32:18 +05:30
|
|
|
if (fd == -1) {
|
2013-05-08 15:57:22 +05:30
|
|
|
log_line("%s: (perform_ip) failed to open interface socket: %s",
|
2012-07-21 03:01:15 +05:30
|
|
|
cl->ifnam, strerror(errno));
|
2010-11-13 01:03:17 +05:30
|
|
|
return;
|
2010-11-12 14:32:18 +05:30
|
|
|
}
|
|
|
|
if (ioctl(fd, SIOCSIFADDR, &ifrt) < 0)
|
2013-05-08 15:57:22 +05:30
|
|
|
log_line("%s: failed to configure IP: %s",
|
2012-07-21 03:01:15 +05:30
|
|
|
cl->ifnam, strerror(errno));
|
2010-11-12 14:32:18 +05:30
|
|
|
close(fd);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Sets the subnet mask on an interface. */
|
2013-05-08 15:57:22 +05:30
|
|
|
void perform_subnet(struct ifchd_client *cl, const char *str, size_t len)
|
2010-11-12 14:32:18 +05:30
|
|
|
{
|
|
|
|
int fd;
|
|
|
|
struct in_addr subnet;
|
|
|
|
struct ifreq ifrt;
|
|
|
|
struct sockaddr_in sin;
|
|
|
|
|
|
|
|
if (!str)
|
2010-11-13 01:03:17 +05:30
|
|
|
return;
|
2012-07-21 03:01:15 +05:30
|
|
|
if (!is_permitted(cl->ifnam))
|
2010-11-13 01:03:17 +05:30
|
|
|
return;
|
2011-07-12 02:41:19 +05:30
|
|
|
if (!inet_pton(AF_INET, str, &subnet))
|
2010-11-13 01:03:17 +05:30
|
|
|
return;
|
2010-11-12 14:32:18 +05:30
|
|
|
|
2013-05-06 16:37:54 +05:30
|
|
|
strnkcpy(ifrt.ifr_name, cl->ifnam, IFNAMSIZ);
|
2010-11-12 14:32:18 +05:30
|
|
|
memset(&sin, 0, sizeof(struct sockaddr));
|
|
|
|
sin.sin_family = AF_INET;
|
|
|
|
sin.sin_addr = subnet;
|
|
|
|
memcpy(&ifrt.ifr_addr, &sin, sizeof(struct sockaddr));
|
|
|
|
|
2011-06-25 20:41:48 +05:30
|
|
|
fd = socket(AF_INET, SOCK_DGRAM, 0);
|
2010-11-12 14:32:18 +05:30
|
|
|
if (fd == -1) {
|
2013-05-08 15:57:22 +05:30
|
|
|
log_line("%s: (perform_ip) failed to open interface socket: %s",
|
2012-07-21 03:01:15 +05:30
|
|
|
cl->ifnam, strerror(errno));
|
2010-11-13 01:03:17 +05:30
|
|
|
return;
|
2010-11-12 14:32:18 +05:30
|
|
|
}
|
|
|
|
if (ioctl(fd, SIOCSIFNETMASK, &ifrt) < 0) {
|
2010-11-13 01:03:17 +05:30
|
|
|
sin.sin_addr.s_addr = 0xffffffff;
|
|
|
|
if (ioctl(fd, SIOCSIFNETMASK, &ifrt) < 0)
|
2013-05-08 15:57:22 +05:30
|
|
|
log_line("%s: failed to configure subnet: %s",
|
2012-07-21 03:01:15 +05:30
|
|
|
cl->ifnam, strerror(errno));
|
2010-11-12 14:32:18 +05:30
|
|
|
}
|
|
|
|
close(fd);
|
|
|
|
}
|
|
|
|
|
2013-05-08 15:57:22 +05:30
|
|
|
void perform_router(struct ifchd_client *cl, const char *str, size_t len)
|
2010-11-12 14:32:18 +05:30
|
|
|
{
|
|
|
|
struct rtentry rt;
|
|
|
|
struct sockaddr_in *dest;
|
|
|
|
struct sockaddr_in *gateway;
|
|
|
|
struct sockaddr_in *mask;
|
|
|
|
struct in_addr router;
|
|
|
|
int fd;
|
|
|
|
|
|
|
|
if (!str)
|
2010-11-13 01:03:17 +05:30
|
|
|
return;
|
2012-07-21 03:01:15 +05:30
|
|
|
if (!is_permitted(cl->ifnam))
|
2010-11-13 01:03:17 +05:30
|
|
|
return;
|
2011-07-12 02:41:19 +05:30
|
|
|
if (!inet_pton(AF_INET, str, &router))
|
2010-11-13 01:03:17 +05:30
|
|
|
return;
|
2010-11-12 14:32:18 +05:30
|
|
|
|
|
|
|
memset(&rt, 0, sizeof(struct rtentry));
|
|
|
|
dest = (struct sockaddr_in *) &rt.rt_dst;
|
|
|
|
dest->sin_family = AF_INET;
|
|
|
|
dest->sin_addr.s_addr = 0x00000000;
|
|
|
|
gateway = (struct sockaddr_in *) &rt.rt_gateway;
|
|
|
|
gateway->sin_family = AF_INET;
|
|
|
|
gateway->sin_addr = router;
|
|
|
|
mask = (struct sockaddr_in *) &rt.rt_genmask;
|
|
|
|
mask->sin_family = AF_INET;
|
|
|
|
mask->sin_addr.s_addr = 0x00000000;
|
|
|
|
|
|
|
|
rt.rt_flags = RTF_UP | RTF_GATEWAY;
|
|
|
|
if (mask->sin_addr.s_addr == 0xffffffff) rt.rt_flags |= RTF_HOST;
|
2012-07-21 03:01:15 +05:30
|
|
|
rt.rt_dev = cl->ifnam;
|
2010-11-12 14:32:18 +05:30
|
|
|
rt.rt_metric = 1;
|
|
|
|
|
2011-06-25 20:41:48 +05:30
|
|
|
fd = socket(AF_INET, SOCK_DGRAM, 0);
|
2010-11-12 14:32:18 +05:30
|
|
|
if (fd == -1) {
|
2013-05-08 15:57:22 +05:30
|
|
|
log_line("%s: (perform_router) failed to open interface socket: %s",
|
2012-07-21 03:01:15 +05:30
|
|
|
cl->ifnam, strerror(errno));
|
2010-11-13 01:03:17 +05:30
|
|
|
return;
|
2010-11-12 14:32:18 +05:30
|
|
|
}
|
2011-05-31 21:25:26 +05:30
|
|
|
if (ioctl(fd, SIOCADDRT, &rt)) {
|
|
|
|
if (errno != EEXIST)
|
2013-05-08 15:57:22 +05:30
|
|
|
log_line("%s: failed to set route: %s",
|
2012-07-21 03:01:15 +05:30
|
|
|
cl->ifnam, strerror(errno));
|
2011-05-31 21:25:26 +05:30
|
|
|
}
|
2010-11-12 14:32:18 +05:30
|
|
|
close(fd);
|
|
|
|
}
|
|
|
|
|
2013-05-08 15:57:22 +05:30
|
|
|
void perform_mtu(struct ifchd_client *cl, const char *str, size_t len)
|
2010-11-12 14:32:18 +05:30
|
|
|
{
|
|
|
|
int fd;
|
|
|
|
unsigned int mtu;
|
|
|
|
struct ifreq ifrt;
|
|
|
|
|
|
|
|
if (!str)
|
2010-11-13 01:03:17 +05:30
|
|
|
return;
|
2012-07-21 03:01:15 +05:30
|
|
|
if (!is_permitted(cl->ifnam))
|
2010-11-13 01:03:17 +05:30
|
|
|
return;
|
2010-11-12 14:32:18 +05:30
|
|
|
|
|
|
|
mtu = strtol(str, NULL, 10);
|
2012-07-21 06:07:41 +05:30
|
|
|
// Minimum MTU for physical IPv4 links is 576 octets.
|
|
|
|
if (mtu < 576)
|
|
|
|
return;
|
2010-11-12 14:32:18 +05:30
|
|
|
ifrt.ifr_mtu = mtu;
|
2013-05-06 16:37:54 +05:30
|
|
|
strnkcpy(ifrt.ifr_name, cl->ifnam, IFNAMSIZ);
|
2010-11-12 14:32:18 +05:30
|
|
|
|
2011-06-25 20:41:48 +05:30
|
|
|
fd = socket(AF_INET, SOCK_DGRAM, 0);
|
2010-11-12 14:32:18 +05:30
|
|
|
if (fd == -1) {
|
2013-05-08 15:57:22 +05:30
|
|
|
log_line("%s: (perform_mtu) failed to open interface socket: %s",
|
2012-07-21 03:01:15 +05:30
|
|
|
cl->ifnam, strerror(errno));
|
2010-11-13 01:03:17 +05:30
|
|
|
return;
|
2010-11-12 14:32:18 +05:30
|
|
|
}
|
|
|
|
if (ioctl(fd, SIOCSIFMTU, &ifrt) < 0)
|
2013-05-08 15:57:22 +05:30
|
|
|
log_line("%s: failed to set MTU (%d): %s", cl->ifnam, mtu,
|
2010-11-13 01:03:17 +05:30
|
|
|
strerror(errno));
|
2010-11-12 14:32:18 +05:30
|
|
|
close(fd);
|
|
|
|
}
|
|
|
|
|
2013-05-08 15:57:22 +05:30
|
|
|
void perform_broadcast(struct ifchd_client *cl, const char *str, size_t len)
|
2010-11-12 14:32:18 +05:30
|
|
|
{
|
|
|
|
int fd;
|
|
|
|
struct in_addr broadcast;
|
|
|
|
struct ifreq ifrt;
|
|
|
|
struct sockaddr_in sin;
|
|
|
|
|
|
|
|
if (!str)
|
2010-11-13 01:03:17 +05:30
|
|
|
return;
|
2012-07-21 03:01:15 +05:30
|
|
|
if (!is_permitted(cl->ifnam))
|
2010-11-13 01:03:17 +05:30
|
|
|
return;
|
2011-07-12 02:41:19 +05:30
|
|
|
if (!inet_pton(AF_INET, str, &broadcast))
|
2010-11-13 01:03:17 +05:30
|
|
|
return;
|
2010-11-12 14:32:18 +05:30
|
|
|
|
2013-05-06 16:37:54 +05:30
|
|
|
strnkcpy(ifrt.ifr_name, cl->ifnam, IFNAMSIZ);
|
2010-11-12 14:32:18 +05:30
|
|
|
memset(&sin, 0, sizeof(struct sockaddr));
|
|
|
|
sin.sin_family = AF_INET;
|
|
|
|
sin.sin_addr = broadcast;
|
|
|
|
memcpy(&ifrt.ifr_addr, &sin, sizeof(struct sockaddr));
|
|
|
|
|
2011-06-25 20:41:48 +05:30
|
|
|
fd = socket(AF_INET, SOCK_DGRAM, 0);
|
2010-11-12 14:32:18 +05:30
|
|
|
if (fd == -1) {
|
2013-05-08 15:57:22 +05:30
|
|
|
log_line("%s: (perform_broadcast) failed to open interface socket: %s", cl->ifnam, strerror(errno));
|
2010-11-13 01:03:17 +05:30
|
|
|
return;
|
2010-11-12 14:32:18 +05:30
|
|
|
}
|
|
|
|
if (ioctl(fd, SIOCSIFBRDADDR, &ifrt) < 0)
|
2013-05-08 15:57:22 +05:30
|
|
|
log_line("%s: failed to set broadcast: %s",
|
2012-07-21 03:01:15 +05:30
|
|
|
cl->ifnam, strerror(errno));
|
2010-11-12 14:32:18 +05:30
|
|
|
close(fd);
|
|
|
|
}
|