/*
 * Server listening routines for the CUPS scheduler.
 *
 * Copyright © 2020-2024 by OpenPrinting.
 * Copyright 2007-2016 by Apple Inc.
 * Copyright 1997-2006 by Easy Software Products, all rights reserved.
 *
 * Licensed under Apache License v2.0.  See the file "LICENSE" for more information.
 */

/*
 * Include necessary headers...
 */

#include "cupsd.h"


/*
 * Make sure the IPV6_V6ONLY is defined on Linux - older versions of
 * glibc don't define it even if the kernel supports it...
 */

#if defined(__linux) && !defined(IPV6_V6ONLY)
#  define IPV6_V6ONLY 26
#endif /* __linux && !IPV6_V6ONLY */


/*
 * 'cupsdDeleteAllListeners()' - Delete all listeners.
 */

void
cupsdDeleteAllListeners(void)
{
  cupsd_listener_t	*lis;		/* Current listening socket */


  for (lis = (cupsd_listener_t *)cupsArrayFirst(Listeners);
       lis;
       lis = (cupsd_listener_t *)cupsArrayNext(Listeners))
#ifdef HAVE_ONDEMAND
    if (!lis->on_demand)
#endif /* HAVE_ONDEMAND */
    {
      cupsArrayRemove(Listeners, lis);
      free(lis);
    }

  if (cupsArrayCount(Listeners) == 0)
  {
    cupsArrayDelete(Listeners);
    Listeners = NULL;
  }
}


/*
 * 'cupsdPauseListening()' - Clear input polling on all listening sockets...
 */

void
cupsdPauseListening(void)
{
  cupsd_listener_t	*lis;		/* Current listening socket */


  if (cupsArrayCount(Listeners) < 1)
    return;

  if (cupsArrayCount(Clients) == MaxClients)
    cupsdLogMessage(CUPSD_LOG_WARN,
                    "Max clients reached, holding new connections...");
  else if (errno == ENFILE || errno == EMFILE)
    cupsdLogMessage(CUPSD_LOG_WARN,
                    "Too many open files, holding new connections for "
		    "30 seconds...");

  cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdPauseListening: Clearing input bits...");

  for (lis = (cupsd_listener_t *)cupsArrayFirst(Listeners);
       lis;
       lis = (cupsd_listener_t *)cupsArrayNext(Listeners))
    cupsdRemoveSelect(lis->fd);

  ListeningPaused = time(NULL) + 30;
}


/*
 * 'cupsdResumeListening()' - Set input polling on all listening sockets...
 */

void
cupsdResumeListening(void)
{
  cupsd_listener_t	*lis;		/* Current listening socket */


  if (cupsArrayCount(Listeners) < 1)
    return;

  cupsdLogMessage(CUPSD_LOG_INFO, "Resuming new connection processing...");
  cupsdLogMessage(CUPSD_LOG_DEBUG2,
                  "cupsdResumeListening: Setting input bits...");

  for (lis = (cupsd_listener_t *)cupsArrayFirst(Listeners);
       lis;
       lis = (cupsd_listener_t *)cupsArrayNext(Listeners))
    cupsdAddSelect(lis->fd, (cupsd_selfunc_t)cupsdAcceptClient, NULL, lis);

  ListeningPaused = 0;
}


/*
 * 'cupsdStartListening()' - Create all listening sockets...
 */

void
cupsdStartListening(void)
{
  int			p;		/* Port number */
  cupsd_listener_t	*lis;		/* Current listening socket */
  char			s[256];		/* String address */
  const char		*have_domain;	/* Have a domain socket? */
  static const char * const encryptions[] =
		{			/* Encryption values */
		  "IfRequested",
		  "Never",
		  "Required",
		  "Always"
		};


  cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdStartListening: %d Listeners",
                  cupsArrayCount(Listeners));

 /*
  * Setup socket listeners...
  */

  for (lis = (cupsd_listener_t *)cupsArrayFirst(Listeners), LocalPort = 0,
           have_domain = NULL;
       lis;
       lis = (cupsd_listener_t *)cupsArrayNext(Listeners))
  {
    httpAddrString(&(lis->address), s, sizeof(s));
    p = httpAddrPort(&(lis->address));

   /*
    * If needed, create a socket for listening...
    */

    if (lis->fd == -1)
    {
     /*
      * Create a socket for listening...
      */

      lis->fd = httpAddrListen(&(lis->address), p);

      if (lis->fd == -1)
      {
	cupsdLogMessage(errno == EAFNOSUPPORT ? CUPSD_LOG_INFO : CUPSD_LOG_ERROR,
			"Unable to open listen socket for address %s:%d - %s.",
			s, p, strerror(errno));

#ifdef AF_INET6
       /*
        * IPv6 is often disabled while DNS returns IPv6 addresses...
	*/

	if (lis->address.addr.sa_family != AF_INET6 &&
	    (FatalErrors & CUPSD_FATAL_LISTEN))
	  cupsdEndProcess(getpid(), 0);
#else
	if (FatalErrors & CUPSD_FATAL_LISTEN)
	  cupsdEndProcess(getpid(), 0);
#endif /* AF_INET6 */

	continue;
      }
    }

    if (p)
      cupsdLogMessage(CUPSD_LOG_INFO, "Listening to %s:%d on fd %d...",
        	      s, p, lis->fd);
    else
      cupsdLogMessage(CUPSD_LOG_INFO, "Listening to %s on fd %d...",
        	      s, lis->fd);

   /*
    * Save the first port that is bound to the local loopback or
    * "any" address...
    */

    if ((!LocalPort || LocalEncryption == HTTP_ENCRYPT_ALWAYS) && p > 0 &&
        (httpAddrLocalhost(&(lis->address)) ||
         httpAddrAny(&(lis->address))))
    {
      LocalPort       = p;
      LocalEncryption = lis->encryption;
    }

#ifdef AF_LOCAL
    if (lis->address.addr.sa_family == AF_LOCAL && !have_domain)
      have_domain = lis->address.un.sun_path;
#endif /* AF_LOCAL */
  }

 /*
  * Make sure that we are listening on localhost!
  */

  if (!LocalPort && !have_domain)
  {
    cupsdLogMessage(CUPSD_LOG_EMERG,
                    "No Listen or Port lines were found to allow access via "
		    "localhost.");

    if (FatalErrors & (CUPSD_FATAL_CONFIG | CUPSD_FATAL_LISTEN))
      cupsdEndProcess(getpid(), 0);
  }

 /*
  * Set the CUPS_SERVER, IPP_PORT, and CUPS_ENCRYPTION variables based on
  * the listeners...
  */

  if (have_domain)
  {
   /*
    * Use domain sockets for the local connection...
    */

    cupsdSetEnv("CUPS_SERVER", have_domain);

    LocalEncryption = HTTP_ENCRYPT_IF_REQUESTED;
  }
  else
  {
   /*
    * Use the default local loopback address for the server...
    */

    cupsdSetEnv("CUPS_SERVER", "localhost");
  }

  cupsdSetEnv("CUPS_ENCRYPTION", encryptions[LocalEncryption]);

  if (LocalPort)
    cupsdSetEnvf("IPP_PORT", "%d", LocalPort);

 /*
  * Resume listening for connections...
  */

  cupsdResumeListening();
}


/*
 * 'cupsdStopListening()' - Close all listening sockets...
 */

void
cupsdStopListening(void)
{
  cupsd_listener_t	*lis;		/* Current listening socket */


  cupsdLogMessage(CUPSD_LOG_DEBUG2,
                  "cupsdStopListening: closing all listen sockets.");

  cupsdPauseListening();

  for (lis = (cupsd_listener_t *)cupsArrayFirst(Listeners);
       lis;
       lis = (cupsd_listener_t *)cupsArrayNext(Listeners))
  {
#ifdef HAVE_ONDEMAND
    if (!lis->on_demand && lis->fd != -1)
    {
      httpAddrClose(&(lis->address), lis->fd);
      lis->fd = -1;
    }

#else
    if (lis->fd != -1)
    {
      httpAddrClose(&(lis->address), lis->fd);
      lis->fd = -1;
    }
#endif /* HAVE_ONDEMAND */
  }
}
