text: /*
   * Authentication code for sample IPP server implementation.
   *
   * Copyright © 2018-2019 by the IEEE-ISTO Printer Working Group
   * Copyright © 2018 by Apple Inc.
   *
   * Licensed under Apache License v2.0.  See the file "LICENSE" for more
   * information.
   */
  
  /*
   * Include necessary headers...
   */
  
  #include "ippserver.h"
  
  #ifndef _WIN32
  #  include <pwd.h>
  #  include <grp.h>
  #endif /* !_WIN32 */
  #ifdef HAVE_LIBPAM
  #  ifdef HAVE_PAM_PAM_APPL_H
  #    include <pam/pam_appl.h>
  #  else
  #    include <security/pam_appl.h>
  #  endif /* HAVE_PAM_PAM_APPL_H */
  #endif /* HAVE_LIBPAM */
  
  
  /*
   * Authentication data...
   */
  
  typedef struct server_authdata_s	/* Authentication data */
  {
    char	username[256],			/* Username string */
  	*password;			/* Password string */
  } server_authdata_t;
  
  
  /*
   * Local functions...
   */
  
  #ifdef HAVE_LIBPAM
  static int	pam_func(int num_msg, const struct pam_message **msg, struct pam_response **resp, server_authdata_t *data);
  #endif /* HAVE_LIBPAM */
  static char	*vcard_escape(const char *s, char *buffer, size_t bufsize);
  
  
  /*
   * 'serverAuthenticateClient()' - Authenticate a client request.
   */
  
  http_status_t				/* O - HTTP_STATUS_CONTINUE on success, other HTTP status on failure */
  serverAuthenticateClient(
      server_client_t *client)		/* I - Client connection */
  {
    http_status_t	status = HTTP_STATUS_CONTINUE;
  					/* Returned status */
    const char	*authorization;		/* Authorization header */
    server_authdata_t data;		/* Authorization data */
    int		userlen;		/* Username:password length */
    char		*password;		/* Pointer to password */
  
  
   /*
    * See if we have anything we can use in the Authorization header...
    */
  
    client->username[0] = '\0';
  
    authorization = httpGetField(client->http, HTTP_FIELD_AUTHORIZATION);
  
  //  serverLogClient(SERVER_LOGLEVEL_DEBUG, client, "Authorization: %s", authorization);
  
    if (!*authorization)
    {
      status = HTTP_STATUS_UNAUTHORIZED;
    }
    else if ((!AuthService && !AuthTestPassword) || strncmp(authorization, "Basic ", 6))
    {
      char	scheme[32],		/* Scheme */
  		*schemeptr;		/* Pointer into scheme */
  
      strlcpy(scheme, authorization, sizeof(scheme));
      if ((schemeptr = strchr(scheme, ' ')) != NULL)
        *schemeptr = '\0';
  
      serverLogClient(SERVER_LOGLEVEL_ERROR, client, "Unsupported authorization scheme \"%s\".", scheme);
      status = HTTP_STATUS_BAD_REQUEST;
    }
    else
    {
     /*
      * OK, what remains is a Basic authorization value.  Parse it and
      * authenticate.
      */
  
      authorization += 5;
      while (_cups_isspace(*authorization & 255))
        authorization ++;
  
      userlen = sizeof(data.username);
      httpDecode64_2(data.username, &userlen, authorization);
  
      if ((password = strchr(data.username, ':')) == NULL)
      {
        serverLogClient(SERVER_LOGLEVEL_ERROR, client, "Missing password.");
        status = HTTP_STATUS_UNAUTHORIZED;
      }
      else
      {
        *password++ = '\0';
        data.password = password;
  
  //      serverLogClient(SERVER_LOGLEVEL_DEBUG, client, "username='%s', password='%s'", data.username, data.password);
  
        if (!data.username[0])
        {
  	serverLogClient(SERVER_LOGLEVEL_ERROR, client, "Empty username.");
  	status = HTTP_STATUS_UNAUTHORIZED;
        }
        else if (!*password)
        {
  	serverLogClient(SERVER_LOGLEVEL_ERROR, client, "Empty password.");
  	status = HTTP_STATUS_UNAUTHORIZED;
        }
        else if (!AuthService)
        {
  	if (strcmp(data.password, AuthTestPassword))
  	{
  	  serverLogClient(SERVER_LOGLEVEL_INFO, client, "Authentication failed.");
  	  status = HTTP_STATUS_UNAUTHORIZED;
  	}
        }
  #ifdef HAVE_LIBPAM
        else
        {
         /*
  	* Authenticate using PAM...
  	*/
  
  	pam_handle_t	*pamh;		/* PAM authentication handle */
  	int		pamerr;		/* PAM error code */
  	struct pam_conv pamdata;	/* PAM conversation data */
  
  	pamdata.conv        = (int (*)(int, const struct pam_message **, struct pam_response **, void *))pam_func;
  	pamdata.appdata_ptr = &data;
  	pamh                = NULL;
  
  	if ((pamerr = pam_start(AuthService, data.username, &pamdata, &pamh)) != PAM_SUCCESS)
  	{
  	  serverLogClient(SERVER_LOGLEVEL_DEBUG, client, "pam_start() returned %d (%s)", pamerr, pam_strerror(pamh, pamerr));
  	}
  
  #  ifdef PAM_RHOST
  	else if ((pamerr = pam_set_item(pamh, PAM_RHOST, client->hostname)) != PAM_SUCCESS)
  	{
  	  serverLogClient(SERVER_LOGLEVEL_DEBUG, client, "pam_set_item(PAM_RHOST) returned %d (%s)", pamerr, pam_strerror(pamh, pamerr));
  	}
  #  endif /* PAM_RHOST */
  
  #  ifdef PAM_TTY
  	else if ((pamerr = pam_set_item(pamh, PAM_TTY, "ippserver")) != PAM_SUCCESS)
  	{
  	  serverLogClient(SERVER_LOGLEVEL_DEBUG, client, "pam_set_item(PAM_TTY) returned %d (%s)", pamerr, pam_strerror(pamh, pamerr));
  	}
  #  endif /* PAM_TTY */
  
  	else if ((pamerr = pam_authenticate(pamh, PAM_SILENT)) != PAM_SUCCESS)
  	{
  	  serverLogClient(SERVER_LOGLEVEL_DEBUG, client, "pam_authenticate() returned %d (%s)", pamerr, pam_strerror(pamh, pamerr));
  	}
  	else if ((pamerr = pam_setcred(pamh, PAM_ESTABLISH_CRED | PAM_SILENT)) != PAM_SUCCESS)
  	{
  	  serverLogClient(SERVER_LOGLEVEL_DEBUG, client, "pam_setcred() returned %d (%s)", pamerr, pam_strerror(pamh, pamerr));
  	}
  	else if ((pamerr = pam_acct_mgmt(pamh, PAM_SILENT)) != PAM_SUCCESS)
  	{
  	  serverLogClient(SERVER_LOGLEVEL_DEBUG, client, "pam_acct_mgmt() returned %d (%s)", pamerr, pam_strerror(pamh, pamerr));
  	}
  
  	if (pamh)
  	  pam_end(pamh, PAM_SUCCESS);
  
  	if (pamerr == PAM_AUTH_ERR)
  	  status = HTTP_STATUS_UNAUTHORIZED;
  	else if (pamerr != PAM_SUCCESS)
  	  status = HTTP_STATUS_SERVER_ERROR;
        }
  
  #else /* !HAVE_LIBPAM */
        else
        {
         /*
  	* No other authentication methods...
  	*/
  
  	serverLogClient(SERVER_LOGLEVEL_INFO, client, "Authentication failed.");
  	status = HTTP_STATUS_SERVER_ERROR;
        }
  #endif /* HAVE_LIBPAM */
      }
    }
  
    if (status == HTTP_STATUS_CONTINUE)
    {
     /*
      * Authentication succeeded!
      */
  
      serverLogClient(SERVER_LOGLEVEL_INFO, client, "Authenticated as \"%s\".", data.username);
  
      strlcpy(client->username, data.username, sizeof(client->username));
    }
  
    return (status);
  }
  
  
  /*
   * 'serverAuthorizeUser()' - Authorize access for an authenticated user.
   */
  
  int					/* O - 1 if authorized, 0 otherwise */
  serverAuthorizeUser(
      server_client_t *client,		/* I - Client connection */
      const char      *owner,		/* I - Object owner or @code NULL@ if none/not applicable */
      gid_t           group,		/* I - Authorized group, if any */
      const char      *scope)		/* I - Access scope */
  {
  #ifndef _WIN32
    struct passwd	*pw;			/* User account information */
    int		i,			/* Looping var */
  		ngroups;		/* Number of groups for user */
  #  ifdef __APPLE__
    int		groups[2048];		/* Group list */
  #  else
    gid_t		groups[2048];		/* Group list */
  #  endif /* __APPLE__ */
  #endif /* !_WIN32 */
  
  
   /*
    * If the scope is "all" or "none", then we are authorized or not regardless
    * of the authenticated user.
    */
  
    if (!strcmp(scope, SERVER_SCOPE_ALL))
    {
      serverLogClient(SERVER_LOGLEVEL_DEBUG, client, "User \"%s\" is authorized because scope is \"all\".", client->username);
      return (1);
    }
    else if (!strcmp(scope, SERVER_SCOPE_NONE))
    {
      serverLogClient(SERVER_LOGLEVEL_DEBUG, client, "User \"%s\" not authorized because scope is \"none\".", client->username);
      return (0);
    }
  
   /*
    * If the request is not authenticated for any other scope, it cannot be
    * authorized.
    */
  
    if (!client->username[0])
    {
      serverLogClient(SERVER_LOGLEVEL_DEBUG, client, "No authenticated user name, not authorized.");
      return (0);
    }
  
   /*
    * The owner is always authorized...
    */
  
    if (owner && !strcmp(client->username, owner) && strcmp(scope, SERVER_SCOPE_ADMIN))
    {
      serverLogClient(SERVER_LOGLEVEL_DEBUG, client, "User \"%s\" is authorized because they are the owner.", client->username);
      return (1);
    }
  
  #ifdef _WIN32
   /*
    * Currently Windows does not support group tests, so everything matches...
    */
  
    serverLogClient(SERVER_LOGLEVEL_DEBUG, client, "User \"%s\" is authorized because groups are currently not validated on Windows.", client->username);
  
    return (1);
  
  #else
   /*
    * If the user does not exist, it cannot be authorized against a group...
    */
  
    if ((pw = getpwnam(client->username)) == NULL)
    {
      serverLogClient(SERVER_LOGLEVEL_DEBUG, client, "User \"%s\" does not have a local account.", client->username);
      return (0);
    }
  
   /*
    * Check group membership...
    */
  
    ngroups = (int)(sizeof(groups) / sizeof(groups[0]));
  
  #  ifdef __APPLE__
    if (getgrouplist(client->username, (int)pw->pw_gid, groups, &ngroups))
  #  else
    if (getgrouplist(client->username, pw->pw_gid, groups, &ngroups))
  #  endif /* __APPLE__ */
    {
      serverLogClient(SERVER_LOGLEVEL_DEBUG, client, "User \"%s\" not authorized because the group list could not be retrieved: %s", client->username, strerror(errno));
      return (0);
    }
  
    if (group != SERVER_GROUP_NONE)
    {
      for (i = 0; i < ngroups; i ++)
        if ((gid_t)groups[i] == group)
          break;
  
      if (i < ngroups)
      {
        serverLogClient(SERVER_LOGLEVEL_DEBUG, client, "User \"%s\" is authorized because they are a group member.", client->username);
        return (1);
      }
    }
  
    if (!strcmp(scope, SERVER_SCOPE_ADMIN))
    {
     /*
      * Authenticate as admin only...
      */
  
      for (i = 0; i < ngroups; i ++)
        if ((gid_t)groups[i] == AuthAdminGroup)
          break;
    }
    else
    {
     /*
      * Authenticate as admin or operator...
      */
  
      for (i = 0; i < ngroups; i ++)
        if ((gid_t)groups[i] == AuthAdminGroup || (gid_t)groups[i] == AuthOperatorGroup)
          break;
    }
  
    if (i < ngroups)
    {
      if ((gid_t)groups[i] == AuthAdminGroup)
        serverLogClient(SERVER_LOGLEVEL_DEBUG, client, "User \"%s\" is authorized because they are an administrator.", client->username);
      else
        serverLogClient(SERVER_LOGLEVEL_DEBUG, client, "User \"%s\" is authorized because they are an operator.", client->username);
  
      return (1);
    }
    else
    {
      serverLogClient(SERVER_LOGLEVEL_DEBUG, client, "User \"%s\" not authorized because they failed the group test.", client->username);
      return (0);
    }
  #endif /* _WIN32 */
  }
  
  
  /*
   * 'serverMakeVCARD()' - Make a VCARD for the named user.
   */
  
  char *					/* O - VCARD as a string */
  serverMakeVCARD(const char *user,	/* I - User name or `NULL` for current user */
                  const char *name,	/* I - Full name or `NULL` */
                  const char *location,	/* I - Office location or `NULL` */
                  const char *email,	/* I - Email address or `NULL` */
                  const char *phone,	/* I - Phone number or `NULL` */
                  char       *buffer,	/* I - Buffer to hold VCARD */
                  size_t     bufsize)	/* I - Size of buffer */
  {
    char		nameval[256],		/* Name value */
  	        locationval[256],	/* Location value */
  		emailval[256],		/* Email address value */
  		phoneval[256];		/* Phone number value (URI) */
  #ifndef _WIN32
    struct passwd	*pw;			/* User information */
    char		gecos[2048],		/* Copy of GECOS information */
  		*gecosval,		/* Current GECOS value */
  		*gecosptr;		/* Pointer into GECOS information */
  
  
    if (user)
      pw = getpwnam(user);
    else
      pw = getpwuid(getuid());
  
    if (pw)
    {
     /*
      * Copy GECOS information from user account, using the format:
      *
      *     NAME,LOCATION,PHONE
      */
  
      strlcpy(gecos, pw->pw_gecos, sizeof(gecos));
      gecosptr = gecos;
  
      gecosval = strsep(&gecosptr, ",");
      if (gecosval && *gecosval && !name)
        name = gecosval;
  
      gecosval = strsep(&gecosptr, ",");
      if (gecosval && *gecosval && !location)
        location = gecosval;
  
      gecosval = strsep(&gecosptr, ",");
      if (gecosval && *gecosval && !phone)
        phone = gecosval;
    }
  #endif /* !_WIN32 */
  
    if (!name || !*name)
      name = cupsUser();
  
    vcard_escape(name, nameval, sizeof(nameval));
  
    if (!location || !*location)
      location = "Unknown location.";
  
    vcard_escape(location, locationval, sizeof(locationval));
  
    if (email && !*email)
      email = NULL;
  
    vcard_escape(email, emailval, sizeof(emailval));
  
    if (phone)
    {
      if (!*phone)
      {
        phone = NULL;
      }
      else
      {
        char uri[256];			/* tel: URI */
  
        httpAssembleURI(HTTP_URI_CODING_ALL, uri, sizeof(uri), "tel", NULL, NULL, 0, phone);
        vcard_escape(uri, phoneval, sizeof(phoneval));
      }
    }
  
    snprintf(buffer, bufsize,
             "BEGIN:VCARD\r\n"
             "VERSION:4.0\r\n"
             "FN:%s\r\n"
             "%s%s%s"
             "%s%s%s"
             "NOTE:%s\r\n"
             "END:VCARD\r\n",
             nameval,
             email ? "EMAIL;TYPE=work:" : "", email ? emailval : "", email ? "\r\n" : "",
             phone ? "TEL;VALUE=uri;TYPE=work:" : "", phone ? phoneval : "", phone ? "\r\n" : "",
             locationval);
  
    return (buffer);
  }
  
  
  #ifdef HAVE_LIBPAM
  /*
   * 'pam_func()' - PAM conversation function.
   */
  
  static int				/* O - Success or failure */
  pam_func(
      int                      num_msg,	/* I - Number of messages */
      const struct pam_message **msg,	/* I - Messages */
      struct pam_response      **resp,	/* O - Responses */
      server_authdata_t        *data)	/* I - Authentication data */
  {
    int			i;		/* Looping var */
    struct pam_response	*replies;	/* Replies */
  
  
   /*
    * Allocate memory for the responses...
    */
  
    if ((replies = calloc((size_t)num_msg, sizeof(struct pam_response))) == NULL)
      return (PAM_CONV_ERR);
  
   /*
    * Answer all of the messages...
    */
  
    for (i = 0; i < num_msg; i ++)
    {
      switch (msg[i]->msg_style)
      {
        case PAM_PROMPT_ECHO_ON :
            replies[i].resp_retcode = PAM_SUCCESS;
            replies[i].resp         = strdup(data->username);
            break;
  
        case PAM_PROMPT_ECHO_OFF :
            replies[i].resp_retcode = PAM_SUCCESS;
            replies[i].resp         = strdup(data->password);
            break;
  
        default :
            replies[i].resp_retcode = PAM_SUCCESS;
            replies[i].resp         = NULL;
            break;
      }
    }
  
   /*
    * Return the responses back to PAM...
    */
  
    *resp = replies;
  
    return (PAM_SUCCESS);
  }
  #endif /* HAVE_LIBPAM */
  
  
  /*
   * 'vcard_escape()' - Escape a string value for use in a VCARD.
   */
  
  static char *				/* O - Escaped string */
  vcard_escape(const char *s,		/* I - String to escape */
               char       *buffer,	/* I - Buffer */
               size_t     bufsize)	/* I - Size of buffer */
  {
    char	*bufptr,			/* Pointer into buffer */
  	*bufend;			/* End of buffer */
  
  
    for (bufptr = buffer, bufend = buffer + bufsize - 2; s && *s && bufptr < bufend; s ++)
    {
     /*
      * Escape COMMA, SEMICOLON, BACKSLASH, and NEWLINE per RFC 6350, Section 3.4.
      */
  
      if (strchr(",;\\\n", *s))
        *bufptr++ = '\\';
  
      if (*s == '\n')
        *bufptr++ = 'n';
      else
        *bufptr++ = *s;
    }
  
    *bufptr = '\0';
  
    return (buffer);
  }
  