/* GNU Mailutils -- a suite of utilities for electronic mail
   Copyright (C) 2011-2025 Free Software Foundation, Inc.

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 3 of the License, or (at your option) any later version.

   This library 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
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General
   Public License along with this library.  If not, see 
   <http://www.gnu.org/licenses/>. */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <limits.h>
#include <mailutils/sockaddr.h>
#include <mailutils/url.h>
#include <mailutils/io.h>
#include <mailutils/errno.h>
#include <mailutils/error.h>
#include <mailutils/nls.h>

static struct mu_sockaddr *
match_sa (struct mu_sockaddr *list, struct sockaddr *sa, socklen_t len)
{
  for (; list; list = list->next)
    if (len == list->addrlen && memcmp (list->addr, sa, len) == 0)
      break;
  return list;
}

int
mu_sockaddr_from_node (struct mu_sockaddr **retval, const char *node,
		       const char *serv, struct mu_sockaddr_hints *mh)
{
  int rc;

  if (!mh)
    {
      static struct mu_sockaddr_hints nullhints = { 0, AF_UNSPEC };
      mh = &nullhints;
    }

  if (mh->family == AF_UNIX)
    {
      size_t slen;
      struct sockaddr_un s_un;

      if (!node)
	return MU_ERR_NONAME;
      slen = strlen (node);
      if (slen >= sizeof s_un.sun_path)
	return MU_ERR_BUFSPACE;

      s_un.sun_family = AF_UNIX;
      strcpy(s_un.sun_path, node);
      return mu_sockaddr_create (retval, (struct sockaddr*) &s_un,
				 sizeof (s_un));
    }
  else 
    {
      struct addrinfo hints;
      struct addrinfo *res, *ap;
      char portbuf[64];
      struct mu_sockaddr *tail = NULL;
      
      memset (&hints, 0, sizeof (hints));
      hints.ai_family = mh->family;
      hints.ai_socktype = mh->socktype;
      hints.ai_protocol = mh->protocol;

      if (!node)
	{
	  if (mh->flags & MU_AH_PASSIVE)
	    hints.ai_flags |= AI_PASSIVE;
	  else
	    return MU_ERR_NONAME;
	}
      if (!serv && mh->port)
	{
	  snprintf (portbuf, sizeof portbuf, "%hu", mh->port);
	  serv = portbuf;
	}
	
      rc = getaddrinfo (node, serv, &hints, &res);

      switch (rc)
	{
	case 0:
	  break;

	case EAI_FAIL:
	  return MU_ERR_GETHOSTBYNAME;
	  
	case EAI_FAMILY:
	  return MU_ERR_FAMILY;

	case EAI_NONAME:
	  return MU_ERR_NONAME;
	  
	case EAI_SERVICE:
	  return MU_ERR_SERVICE;

	case EAI_SYSTEM:
	  mu_error (_("%s:%s: cannot parse address: %s"),
		    node, serv, mu_strerror (errno));
	  return errno;
			
	case EAI_BADFLAGS:
	  return MU_ERR_BADFLAGS;
	  
	case EAI_SOCKTYPE:
	  return MU_ERR_SOCKTYPE;
			
	case EAI_MEMORY:
	  return ENOMEM;
			
	default:
	  mu_error ("%s:%s: %s", node, serv, gai_strerror (rc));
	  return MU_ERR_FAILURE;
	}

      *retval = NULL;
      for (ap = res; ap; ap = ap->ai_next)
	if (mh->family == AF_UNSPEC || ap->ai_addr->sa_family == mh->family)
	  {
	    struct mu_sockaddr *sa;

	    if (match_sa (*retval, ap->ai_addr, ap->ai_addrlen))
	      continue;
	    rc = mu_sockaddr_create (&sa, ap->ai_addr, ap->ai_addrlen);
	    if (rc)
	      {
		mu_sockaddr_free_list (*retval);
		freeaddrinfo (res);
		return rc;
	      }
	    if (tail)
	      mu_sockaddr_insert (tail, sa, 0);
	    else
	      *retval = sa;
	    tail = sa;
	  }
      freeaddrinfo (res);
    }
  return 0;
}
