/* -*- c-indent-level: 2; c-continued-statement-offset: 2 -*- */
/*
 * %CopyrightBegin%
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Copyright Ericsson AB 1998-2025. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * %CopyrightEnd%
 */
/* 
 * This file is for internal use within epmd.
 */

/* This file don't depend on "sys.h" so we have to do some target
   definitions ourselves */

#ifdef __WIN32__
#define NO_SYSCONF
#define NO_DAEMON

/*
 * Increase FD_SETSIZE from 64 to 1024 by defining it before including
 * <winsock2.h>.
 *
 * https://learn.microsoft.com/en-us/windows/win32/winsock/maximum-number-of-sockets-supported-2
 */
#define FD_SETSIZE 1024

#endif

/* ************************************************************************ */
/* Standard includes                                                        */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifdef __WIN32__
#  ifndef WINDOWS_H_INCLUDES_WINSOCK2_H
#    include <winsock2.h>
#  endif
#  include <ws2tcpip.h>
#  include <windows.h>
#  include <process.h>
#endif

#include <sys/types.h>
#include <fcntl.h>

#ifndef __WIN32__
#  include <time.h>
#  ifdef HAVE_SYS_TIME_H
#     include <sys/time.h>
#  endif
#endif

#if !defined(__WIN32__)
#  include <netinet/in.h>
#  include <sys/socket.h>
#  include <sys/stat.h>

#  ifdef DEF_INADDR_LOOPBACK_IN_RPC_TYPES_H
#    include <rpc/types.h>
#  endif

#  include <arpa/inet.h>
#  include <netinet/tcp.h>
#endif /* ! WIN32 */

#include <ctype.h>
#include <signal.h>
#include <errno.h>

#ifdef HAVE_SYSLOG_H
#  include <syslog.h>
#endif

#ifdef SYS_SELECT_H
#  include <sys/select.h>
#endif

#ifdef HAVE_UNISTD_H
#  include <unistd.h>
#endif

#include <stdarg.h>

#ifdef HAVE_SYSTEMD_DAEMON
#  include <systemd/sd-daemon.h>
#endif /* HAVE_SYSTEMD_DAEMON */

#ifdef DEBUG
#define ASSERT(Cnd) do { if (!(Cnd)) { abort(); } } while(0)
#else
#define ASSERT(Cnd)
#endif

#if defined(HAVE_IN6) && defined(AF_INET6) && defined(HAVE_INET_PTON)
#  define EPMD6
#endif

/* ************************************************************************ */
/* Replace some functions by others by making the function name a macro */

#ifdef __WIN32__
#  define close(s) closesocket((s))
#  define write(a,b,c) send((a),(b),(c),0)
#  define read(a,b,c) recv((a),(char *)(b),(c),0)
#  define sleep(s) Sleep((s) * 1000)
#  define ioctl(s,r,o) ioctlsocket((s),(r),(o))
#endif /* WIN32 */

#ifdef USE_BCOPY
#  define memcpy(a, b, c) bcopy((b), (a), (c))
#  define memcmp(a, b, c) bcmp((a), (b), (c))
#  define memzero(buf, len) bzero((buf), (len))
#else
#  define memzero(buf, len) memset((buf), '\0', (len))
#endif

/* ************************************************************************ */
/* Try to find replacement values for undefined system parameters           */

#if defined(__WIN32__) && !defined(EADDRINUSE)
#  define EADDRINUSE WSAEADDRINUSE
#endif

#if defined(__WIN32__) && !defined(ECONNABORTED)
#  define ECONNABORTED WSAECONNABORTED
#endif

#ifndef SOMAXCONN
#  define SOMAXCONN 128
#endif

/*
 * How to get max no of file descriptors? We used to use NOFILE from
 * <sys/param.h>, but that tends to have little relation to reality.
 * Best is to use sysconf() (POSIX), but we'll just punt if that isn't
 * available. Start out with a high value because it will also be
 * used as the number of file descriptors given to select() (it's is
 * a terrible bug not to have all file descriptors included in the select()).
 * The value will be adjusted down if FD_SETSIZE is smaller.
 */

#define MAX_FILES 2048		/* if sysconf() isn't available, or fails */

/* ************************************************************************ */
/* Macros that let us use IPv6                                              */

#if HAVE_IN6
#  if ! defined(HAVE_IN6ADDR_ANY) || ! HAVE_IN6ADDR_ANY
#    if HAVE_DECL_IN6ADDR_ANY_INIT
static const struct in6_addr in6addr_any = { { IN6ADDR_ANY_INIT } };
#    else
static const struct in6_addr in6addr_any =
    { { { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 } } };
#    endif /* HAVE_IN6ADDR_ANY_INIT */
#  endif /* ! HAVE_DECL_IN6ADDR_ANY */

#  if ! defined(HAVE_IN6ADDR_LOOPBACK) || ! HAVE_IN6ADDR_LOOPBACK
#    if HAVE_DECL_IN6ADDR_LOOPBACK_INIT
static const struct in6_addr in6addr_loopback =
    { { IN6ADDR_LOOPBACK_INIT } };
#    else
static const struct in6_addr in6addr_loopback =
    { { { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1 } } };
#    endif /* HAVE_IN6ADDR_LOOPBACK_INIT */
#  endif /* ! HAVE_DECL_IN6ADDR_LOOPBACK */
#endif /* HAVE_IN6 */

#define IS_ADDR_LOOPBACK(addr) ((addr).s_addr == htonl(INADDR_LOOPBACK))

#if defined(EPMD6)

#define EPMD_SOCKADDR_IN sockaddr_storage
#define FAMILY AF_INET6

#define SET_ADDR6(dst, addr, port) do { \
    struct sockaddr_in6 *sa = (struct sockaddr_in6 *)&(dst); \
    memset(sa, 0, sizeof(dst)); \
    sa->sin6_family = AF_INET6; \
    sa->sin6_addr = (addr); \
    sa->sin6_port = htons(port); \
 } while(0)

#define SET_ADDR(dst, addr, port) do { \
    struct sockaddr_in *sa = (struct sockaddr_in *)&(dst); \
    memset(sa, 0, sizeof(dst)); \
    sa->sin_family = AF_INET; \
    sa->sin_addr.s_addr = (addr); \
    sa->sin_port = htons(port); \
 } while(0)

#else /* Not IP v6 */

#define EPMD_SOCKADDR_IN sockaddr_in
#define FAMILY AF_INET

#define SET_ADDR(dst, addr, port) do { \
    memset((char*)&(dst), 0, sizeof(dst)); \
    (dst).sin_family = AF_INET; \
    (dst).sin_addr.s_addr = (addr); \
    (dst).sin_port = htons(port); \
 } while(0)

#endif /* Not IP v6 */

/* ************************************************************************ */
/* Our own definitions                                                      */

#define EPMD_FALSE 0
#define EPMD_TRUE 1

/* If no activity we let select() return every IDLE_TIMEOUT second
   A file descriptor that has been idle for CLOSE_TIMEOUT seconds and
   isn't an ALIVE socket has probably hanged and should be closed */

#define IDLE_TIMEOUT 5
#define CLOSE_TIMEOUT 60

/* We save the name of nodes that are unregistered. If a new
   node register the name we want to increment the "creation",
   a constant 1..3. But we put an limit to this saving to keep
   the lookup fast and not to leak memory. */

#define MAX_UNREG_COUNT 1000
#define DEBUG_MAX_UNREG_COUNT 5

/*
 * Maximum length of a node name == atom name
 *   255 characters; UTF-8 encoded -> max 255*4
 */
#define MAXSYMLEN (255*4)

#define MAX_LISTEN_SOCKETS 16

/*
 * Largest request: ALIVE2_REQ
 *  2 + 13 + 2*MAXSYMLEN
 * Largest response: PORT2_RESP
 *  2 + 14 + 2*MAXSYMLEN
 *
 * That is, 3*MAXSYMLEN should be large enough
 */

#define INBUF_SIZE (3*MAXSYMLEN)
#define OUTBUF_SIZE (3*MAXSYMLEN)

#define get_int16(s) ((((unsigned char*)  (s))[0] << 8) | \
                      (((unsigned char*)  (s))[1]))

#define put_int16(i, s) {((unsigned char*)(s))[0] = ((i) >> 8) & 0xff; \
                        ((unsigned char*)(s))[1] = (i)         & 0xff;}

#define put_int32(i, s) do {((char*)(s))[0] = (char)((i) >> 24) & 0xff;   \
                            ((char*)(s))[1] = (char)((i) >> 16) & 0xff;   \
                            ((char*)(s))[2] = (char)((i) >> 8)  & 0xff;   \
                            ((char*)(s))[3] = (char)(i)         & 0xff;} \
                        while (0)

#if defined(__GNUC__)
#  define EPMD_INLINE __inline__
#elif defined(__WIN32__)
#  define EPMD_INLINE __inline
#else
#  define EPMD_INLINE
#endif

/* ************************************************************************ */

/* Structures used by server */

typedef struct {
  int fd;			/* File descriptor */
  unsigned char open;		/* TRUE if open */
  unsigned char keep;		/* Don't close when sent reply */
  unsigned char local_peer;     /* The peer of this connection is via
                                   loopback interface */
  unsigned got;			/* # of bytes we have got */
  unsigned want;		/* Number of bytes we want */
  char *buf;			/* The remaining buffer */

  time_t mod_time;		/* Last activity on this socket */
} Connection;

struct enode {
  struct enode *next;
  int fd;			/* The socket in use */
  unsigned short port;		/* Port number of Erlang node */
  char symname[MAXSYMLEN+1];	/* Name of the Erlang node */
  unsigned int cr_counter;	/* Used to generate 'creation' numbers */
  char nodetype;                /* 77 = normal erlang node 72 = hidden (c-node */
  char protocol;                /* 0 = tcp/ipv4 */
  unsigned short highvsn;       /* 5: creation=1..3, 6: creation=1..(2^32-1)*/
  unsigned short lowvsn;
  int extralen;
  char extra[MAXSYMLEN+1];
};

typedef struct enode Node;

typedef struct {
  Node *reg;
  Node *unreg;
  Node *unreg_tail;
  int unreg_count;
} Nodes;


/* This is the structure with all variables needed to pass on
   to all functions. This makes this program reentrant */

typedef struct {
  int port;
  int debug;
  int silent; 
  int is_daemon;
  int brutal_kill;
  unsigned packet_timeout;
  unsigned delay_accept;
  unsigned delay_write;
  int max_conn;
  int active_conn;
  int select_fd_top;
  char *progname;
  Connection *conn;
  Nodes nodes;
  fd_set orig_read_mask;
  int listenfd[MAX_LISTEN_SOCKETS];
  char *addresses;
  char **argv;
#ifdef HAVE_SYSTEMD_DAEMON
  int is_systemd;
#endif /* HAVE_SYSTEMD_DAEMON */
} EpmdVars;

void dbg_printf(EpmdVars*,int,const char*,...);
void dbg_tty_printf(EpmdVars*,int,const char*,...);
void dbg_perror(EpmdVars*,const char*,...);
void kill_epmd(EpmdVars*);
void epmd_call(EpmdVars*,int);
void run(EpmdVars*);
void epmd_cleanup_exit(EpmdVars*, int);
int epmd_conn_close(EpmdVars*,Connection*);
void stop_cli(EpmdVars *g, char *name);

#ifdef DONT_USE_MAIN
int  start_epmd(char *,char *,char *,char *,char *,char *,char *,char *,char *,char *);
int  epmd(int,char **);
int  epmd_dbg(int,int);
#endif


