/*
 * Copyright (C) 1995,1996,1997 Lars Fenneberg
 *
 * Copyright 1992 Livingston Enterprises, Inc.
 *
 * Copyright 1992,1993, 1994,1995 The Regents of the University of Michigan
 * and Merit Network, Inc. All Rights Reserved
 *
 * See the file COPYRIGHT for the respective terms and conditions.
 * If the file is missing contact me at lf@elemental.net
 * and I'll send you a copy.
 *
 */

#define _GNU_SOURCE

#include <sys/time.h>

#include <config.h>
#include <includes.h>
#include <radcli/radcli.h>
#include "util.h"

#define	RC_BUFSIZ	1024

#ifdef __linux__
#include <sched.h>
#define NSNET_SZ (128)
#endif


static char const * months[] =
		{
			"Jan", "Feb", "Mar", "Apr", "May", "Jun",
			"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
		};

/*- Turns printable string into correct tm struct entries
 *
 * @param valstr the printable date in 'day month year' format.
 * @param tm the output struct.
 * @return 0 on success, -1 if the month name in valstr was not recognized
 *  (tm is left untouched in that case).
 -*/
int rc_str2tm (char const *valstr, struct tm *tm)
{
	int             i;

	/* Get the month */
	for (i = 0; i < 12; i++)
	{
		if (strncmp (months[i], valstr, 3) == 0)
			break;
	}
	if (i == 12)
		return -1;
	tm->tm_mon = i;

	/* Get the Day */
	tm->tm_mday = atoi (&valstr[4]);

	/* Now the year */
	tm->tm_year = atoi (&valstr[7]) - 1900;

	return 0;
}

/*- Returns the current monotonic time as a double
 *
 * @return Get monotonic time seconds since some fixed start) expressed as
 * 	double-precision floating point number.
 -*/
double rc_getmtime(void)
{
#ifdef HAVE_CLOCK_GETTIME
    struct timespec timespec;

    if (clock_gettime(CLOCK_MONOTONIC, &timespec) != 0)
        return -1;

    return timespec.tv_sec + ((double)timespec.tv_nsec) / 1000000000.0;
#else
    struct timeval timev;

    if (gettimeofday(&timev, NULL) == -1)
        return -1;

    return timev.tv_sec + ((double)timev.tv_usec) / 1000000.0;
#endif
}

/** @brief Generate a "unique" session-ID string
 *
 * @deprecated This function is retained only for source compatibility with
 * freeradius-client and radiusclient-ng.  It returns a pointer to a static
 * non-reentrant buffer that is overwritten on each call, making it unsafe in
 * multi-threaded applications.  Use @c snprintf into your own buffer instead:
 * @code
 * char sid[15];
 * snprintf(sid, sizeof(sid), "%08lX%04X", (unsigned long)time(NULL), getpid());
 * @endcode
 *
 * @return pointer to a static buffer containing the ID string; overwritten by
 *   subsequent calls.
 */
char *
rc_mksid (void)
{
  static char buf[15];
  static unsigned short int cnt = 0;
  snprintf (buf, sizeof(buf), "%08lX%04X%02hX",
	   (unsigned long int) time (NULL),
	   (unsigned int) getpid (),
	   cnt & 0xFF);
  cnt++;
  return buf;
}
/*
 * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * Copyright 2006  The FreeRADIUS server project
 */

#ifdef RC_NEED_STRLCPY

/*-
 * Copy src to string dst of size siz.  At most siz-1 characters
 * will be copied.  Always NUL terminates (unless siz == 0).
 * Returns strlen(src); if retval >= siz, truncation occurred.
 -*/
size_t
rc_strlcpy(char *dst, char const *src, size_t siz)
{
    char *d = dst;
    char const *s = src;
    size_t n = siz;

    /* Copy as many bytes as will fit */
    if (n != 0 && --n != 0) {
        do {
            if ((*d++ = *s++) == 0)
                break;
        } while (--n != 0);
    }

    /* Not enough room in dst, add NUL and traverse rest of src */
    if (n == 0) {
        if (siz != 0)
            *d = '\0';      /* NUL-terminate dst */
        while (*s++)
            ;
    }

    return(s - src - 1);    /* count does not include NUL */
}

#endif /* RC_NEED_STRLCPY */

/** Set the network namespace for the current thread (or process - if single threaded).
 *
 * @param net_namespace - New network namespace to set.
 * @param prev_ns_handle - Handle to previous network namespace.
 *
 * @return 0 on success, -1 when failure.
 */
int rc_set_netns(const char *net_namespace, int *prev_ns_handle)
{
    int rc = 0;
#ifdef __linux__
    rc = -1;
    static const char* crt_nsnet = "/proc/self/ns/net";
    int sock_ns_fd = -1;
    char sock_nsnet[NSNET_SZ];

    if (NULL == net_namespace) {
        rc_log(LOG_ERR, "Namespace not provided");
        return rc;
    }
    if (NULL == prev_ns_handle) {
        rc_log(LOG_ERR, "NULL NS handle for: %s", net_namespace);
        return rc;
    }
    *prev_ns_handle = -1;
    snprintf(sock_nsnet, NSNET_SZ, "/var/run/netns/%s", net_namespace);
    do {
        *prev_ns_handle = open(crt_nsnet, O_RDONLY);
        if (*prev_ns_handle < 0) {
            rc_log(LOG_ERR, "Cannot open %s errno=%s(%d)", crt_nsnet, strerror (errno), errno);
            break;
        }
        sock_ns_fd = open(sock_nsnet, O_RDONLY);
        if (sock_ns_fd < 0) {
            rc_log(LOG_ERR, "Cannot open %s errno=%s(%d)", sock_nsnet, strerror (errno), errno);
            break;
        }
        if (setns(sock_ns_fd, CLONE_NEWNET) < 0) {
            rc_log(LOG_ERR, "'setns' set failed for %s errno=%s(%d)", sock_nsnet,
            strerror(errno), errno);
            break;
        }
        rc = 0;
    }while (0);
    if (sock_ns_fd >= 0) {
        (void)close(sock_ns_fd);
    }
    if (rc != 0) {
        if (*prev_ns_handle >=0 ) {
            (void)close(*prev_ns_handle);
            *prev_ns_handle = -1;
        }
    }
#else    
    rc_log(LOG_ERR, "Not a Linux system. No operation performed");
#endif
    return rc;
}

/** Reset the network name space for the current thread (or process - if single threaded).
 *  'prev_ns_handle' becomes invalid after this call.
 *
 * @param  prev_ns_handle - Handle to previous network name space.
 *
 * @return 0 on success, -1 when failure.
 */
int rc_reset_netns(int *prev_ns_handle)
{
    int rc = 0;
#ifdef __linux__
    if (NULL == prev_ns_handle) {
        rc_log(LOG_ERR, "NULL NS handle");
        return -1;
    }
    if (setns(*prev_ns_handle, CLONE_NEWNET) < 0) {
        rc_log(LOG_ERR, "'setns' - reset failed errno=%s(%d)", strerror (errno), errno);
    }
    if (close(*prev_ns_handle) != 0) {
        rc_log(LOG_ERR, "Close error fd=%d errno=%s(%d)", *prev_ns_handle, strerror (errno), errno);
        rc = -1;
    }
    *prev_ns_handle = -1;
#else    
    rc_log(LOG_ERR, "Not a Linux system. No operation performed");
#endif    
    return rc;
}
