2022-05-13 20:08:20 +08:00
|
|
|
|
/*
|
|
|
|
|
* Authentication certificate routines for the CUPS scheduler.
|
|
|
|
|
*
|
2023-01-12 15:27:08 +08:00
|
|
|
|
* Copyright © 2021-2022 by OpenPrinting.
|
|
|
|
|
* Copyright © 2007-2016 by Apple Inc.
|
|
|
|
|
* Copyright © 1997-2006 by Easy Software Products.
|
2022-05-13 20:08:20 +08:00
|
|
|
|
*
|
2023-01-12 15:27:08 +08:00
|
|
|
|
* Licensed under Apache License v2.0. See the file "LICENSE" for more
|
|
|
|
|
* information.
|
2022-05-13 20:08:20 +08:00
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Include necessary headers...
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include "cupsd.h"
|
|
|
|
|
#ifdef HAVE_ACL_INIT
|
|
|
|
|
# include <sys/acl.h>
|
|
|
|
|
# ifdef HAVE_MEMBERSHIP_H
|
|
|
|
|
# include <membership.h>
|
|
|
|
|
# endif /* HAVE_MEMBERSHIP_H */
|
|
|
|
|
#endif /* HAVE_ACL_INIT */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Local functions...
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
static int ctcompare(const char *a, const char *b);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* 'cupsdAddCert()' - Add a certificate.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
cupsdAddCert(int pid, /* I - Process ID */
|
|
|
|
|
const char *username, /* I - Username */
|
|
|
|
|
int type) /* I - AuthType for username */
|
|
|
|
|
{
|
|
|
|
|
int i; /* Looping var */
|
|
|
|
|
cupsd_cert_t *cert; /* Current certificate */
|
|
|
|
|
int fd; /* Certificate file */
|
|
|
|
|
char filename[1024]; /* Certificate filename */
|
|
|
|
|
static const char hex[] = "0123456789ABCDEF";
|
|
|
|
|
/* Hex constants... */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdAddCert: Adding certificate for PID %d", pid);
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Allocate memory for the certificate...
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
if ((cert = calloc(sizeof(cupsd_cert_t), 1)) == NULL)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Fill in the certificate information...
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
cert->pid = pid;
|
|
|
|
|
cert->type = type;
|
|
|
|
|
strlcpy(cert->username, username, sizeof(cert->username));
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < 32; i ++)
|
|
|
|
|
cert->certificate[i] = hex[CUPS_RAND() & 15];
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Save the certificate to a file readable only by the User and Group
|
|
|
|
|
* (or root and SystemGroup for PID == 0)...
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
snprintf(filename, sizeof(filename), "%s/certs/%d", StateDir, pid);
|
|
|
|
|
unlink(filename);
|
|
|
|
|
|
|
|
|
|
if ((fd = open(filename, O_WRONLY | O_CREAT | O_EXCL, 0400)) < 0)
|
|
|
|
|
{
|
|
|
|
|
cupsdLogMessage(CUPSD_LOG_ERROR,
|
|
|
|
|
"Unable to create certificate file %s - %s",
|
|
|
|
|
filename, strerror(errno));
|
|
|
|
|
free(cert);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (pid == 0)
|
|
|
|
|
{
|
2023-01-12 15:27:08 +08:00
|
|
|
|
#if defined(HAVE_ACL_INIT) && !CUPS_SNAP
|
2022-05-13 20:08:20 +08:00
|
|
|
|
acl_t acl; /* ACL information */
|
|
|
|
|
acl_entry_t entry; /* ACL entry */
|
|
|
|
|
acl_permset_t permset; /* Permissions */
|
|
|
|
|
# ifdef HAVE_MBR_UID_TO_UUID
|
|
|
|
|
uuid_t group; /* Group ID */
|
|
|
|
|
# endif /* HAVE_MBR_UID_TO_UUID */
|
|
|
|
|
static int acls_not_supported = 0;
|
|
|
|
|
/* Only warn once */
|
2023-01-12 15:27:08 +08:00
|
|
|
|
#endif /* HAVE_ACL_INIT && !CUPS_SNAP */
|
2022-05-13 20:08:20 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Root certificate...
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
fchmod(fd, 0440);
|
2023-01-12 15:27:08 +08:00
|
|
|
|
|
|
|
|
|
/* ACLs do not work when cupsd is running in a Snap, and certificates
|
|
|
|
|
need root as group owner to be only accessible for CUPS and not the
|
|
|
|
|
unprivileged sub-processes */
|
|
|
|
|
#if CUPS_SNAP
|
|
|
|
|
fchown(fd, RunUser, 0);
|
|
|
|
|
#else
|
2022-05-13 20:08:20 +08:00
|
|
|
|
fchown(fd, RunUser, SystemGroupIDs[0]);
|
|
|
|
|
|
|
|
|
|
cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdAddCert: NumSystemGroups=%d", NumSystemGroups);
|
|
|
|
|
|
2023-01-12 15:27:08 +08:00
|
|
|
|
# ifdef HAVE_ACL_INIT
|
2022-05-13 20:08:20 +08:00
|
|
|
|
if (NumSystemGroups > 1)
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* Set POSIX ACLs for the root certificate so that all system
|
|
|
|
|
* groups can access it...
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
int j; /* Looping var */
|
|
|
|
|
|
2023-01-12 15:27:08 +08:00
|
|
|
|
# ifdef HAVE_MBR_UID_TO_UUID
|
2022-05-13 20:08:20 +08:00
|
|
|
|
/*
|
|
|
|
|
* On macOS, ACLs use UUIDs instead of GIDs...
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
acl = acl_init(NumSystemGroups - 1);
|
|
|
|
|
|
|
|
|
|
for (i = 1; i < NumSystemGroups; i ++)
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* Add each group ID to the ACL...
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
for (j = 0; j < i; j ++)
|
|
|
|
|
if (SystemGroupIDs[j] == SystemGroupIDs[i])
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
if (j < i)
|
|
|
|
|
continue; /* Skip duplicate groups */
|
|
|
|
|
|
|
|
|
|
acl_create_entry(&acl, &entry);
|
|
|
|
|
acl_get_permset(entry, &permset);
|
|
|
|
|
acl_add_perm(permset, ACL_READ_DATA);
|
|
|
|
|
acl_set_tag_type(entry, ACL_EXTENDED_ALLOW);
|
|
|
|
|
mbr_gid_to_uuid((gid_t)SystemGroupIDs[i], group);
|
|
|
|
|
acl_set_qualifier(entry, &group);
|
|
|
|
|
acl_set_permset(entry, permset);
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-12 15:27:08 +08:00
|
|
|
|
# else
|
2022-05-13 20:08:20 +08:00
|
|
|
|
/*
|
|
|
|
|
* POSIX ACLs need permissions for owner, group, other, and mask
|
|
|
|
|
* in addition to the rest of the system groups...
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
acl = acl_init(NumSystemGroups + 3);
|
|
|
|
|
|
|
|
|
|
/* Owner */
|
|
|
|
|
acl_create_entry(&acl, &entry);
|
|
|
|
|
acl_get_permset(entry, &permset);
|
|
|
|
|
acl_add_perm(permset, ACL_READ);
|
|
|
|
|
acl_set_tag_type(entry, ACL_USER_OBJ);
|
|
|
|
|
acl_set_permset(entry, permset);
|
|
|
|
|
|
|
|
|
|
/* Group */
|
|
|
|
|
acl_create_entry(&acl, &entry);
|
|
|
|
|
acl_get_permset(entry, &permset);
|
|
|
|
|
acl_add_perm(permset, ACL_READ);
|
|
|
|
|
acl_set_tag_type(entry, ACL_GROUP_OBJ);
|
|
|
|
|
acl_set_permset(entry, permset);
|
|
|
|
|
|
|
|
|
|
/* Others */
|
|
|
|
|
acl_create_entry(&acl, &entry);
|
|
|
|
|
acl_get_permset(entry, &permset);
|
|
|
|
|
acl_add_perm(permset, 0);
|
|
|
|
|
acl_set_tag_type(entry, ACL_OTHER);
|
|
|
|
|
acl_set_permset(entry, permset);
|
|
|
|
|
|
|
|
|
|
/* Mask */
|
|
|
|
|
acl_create_entry(&acl, &entry);
|
|
|
|
|
acl_get_permset(entry, &permset);
|
|
|
|
|
acl_add_perm(permset, ACL_READ);
|
|
|
|
|
acl_set_tag_type(entry, ACL_MASK);
|
|
|
|
|
acl_set_permset(entry, permset);
|
|
|
|
|
|
|
|
|
|
for (i = 1; i < NumSystemGroups; i ++)
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* Add each group ID to the ACL...
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
for (j = 0; j < i; j ++)
|
|
|
|
|
if (SystemGroupIDs[j] == SystemGroupIDs[i])
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
if (j < i)
|
|
|
|
|
continue; /* Skip duplicate groups */
|
|
|
|
|
|
|
|
|
|
acl_create_entry(&acl, &entry);
|
|
|
|
|
acl_get_permset(entry, &permset);
|
|
|
|
|
acl_add_perm(permset, ACL_READ);
|
|
|
|
|
acl_set_tag_type(entry, ACL_GROUP);
|
|
|
|
|
acl_set_qualifier(entry, SystemGroupIDs + i);
|
|
|
|
|
acl_set_permset(entry, permset);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (acl_valid(acl))
|
|
|
|
|
{
|
|
|
|
|
char *text, *textptr; /* Temporary string */
|
|
|
|
|
|
|
|
|
|
cupsdLogMessage(CUPSD_LOG_ERROR, "ACL did not validate: %s",
|
|
|
|
|
strerror(errno));
|
|
|
|
|
text = acl_to_text(acl, NULL);
|
|
|
|
|
for (textptr = strchr(text, '\n');
|
|
|
|
|
textptr;
|
|
|
|
|
textptr = strchr(textptr + 1, '\n'))
|
|
|
|
|
*textptr = ',';
|
|
|
|
|
|
|
|
|
|
cupsdLogMessage(CUPSD_LOG_ERROR, "ACL: %s", text);
|
|
|
|
|
acl_free(text);
|
|
|
|
|
}
|
2023-01-12 15:27:08 +08:00
|
|
|
|
# endif /* HAVE_MBR_UID_TO_UUID */
|
2022-05-13 20:08:20 +08:00
|
|
|
|
|
|
|
|
|
if (acl_set_fd(fd, acl))
|
|
|
|
|
{
|
|
|
|
|
if (errno != EOPNOTSUPP || !acls_not_supported)
|
|
|
|
|
cupsdLogMessage(CUPSD_LOG_ERROR,
|
|
|
|
|
"Unable to set ACLs on root certificate \"%s\" - %s",
|
|
|
|
|
filename, strerror(errno));
|
|
|
|
|
|
|
|
|
|
if (errno == EOPNOTSUPP)
|
|
|
|
|
acls_not_supported = 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
acl_free(acl);
|
|
|
|
|
}
|
2023-01-12 15:27:08 +08:00
|
|
|
|
# endif /* HAVE_ACL_INIT */
|
|
|
|
|
#endif /* CUPS_SNAP */
|
2022-05-13 20:08:20 +08:00
|
|
|
|
|
|
|
|
|
RootCertTime = time(NULL);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* CGI certificate...
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
fchmod(fd, 0400);
|
|
|
|
|
fchown(fd, User, Group);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
write(fd, cert->certificate, strlen(cert->certificate));
|
|
|
|
|
close(fd);
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Insert the certificate at the front of the list...
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
cert->next = Certs;
|
|
|
|
|
Certs = cert;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* 'cupsdDeleteCert()' - Delete a single certificate.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
cupsdDeleteCert(int pid) /* I - Process ID */
|
|
|
|
|
{
|
|
|
|
|
cupsd_cert_t *cert, /* Current certificate */
|
|
|
|
|
*prev; /* Previous certificate */
|
|
|
|
|
char filename[1024]; /* Certificate file */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (prev = NULL, cert = Certs; cert != NULL; prev = cert, cert = cert->next)
|
|
|
|
|
if (cert->pid == pid)
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* Remove this certificate from the list...
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdDeleteCert: Removing certificate for PID %d.", pid);
|
|
|
|
|
|
|
|
|
|
if (prev == NULL)
|
|
|
|
|
Certs = cert->next;
|
|
|
|
|
else
|
|
|
|
|
prev->next = cert->next;
|
|
|
|
|
|
|
|
|
|
free(cert);
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Delete the file and return...
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
snprintf(filename, sizeof(filename), "%s/certs/%d", StateDir, pid);
|
|
|
|
|
if (unlink(filename))
|
|
|
|
|
cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to remove %s!", filename);
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* 'cupsdDeleteAllCerts()' - Delete all certificates...
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
cupsdDeleteAllCerts(void)
|
|
|
|
|
{
|
|
|
|
|
cupsd_cert_t *cert, /* Current certificate */
|
|
|
|
|
*next; /* Next certificate */
|
|
|
|
|
char filename[1024]; /* Certificate file */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Loop through each certificate, deleting them...
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
for (cert = Certs; cert != NULL; cert = next)
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* Delete the file...
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
snprintf(filename, sizeof(filename), "%s/certs/%d", StateDir, cert->pid);
|
|
|
|
|
if (unlink(filename))
|
|
|
|
|
cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to remove %s!", filename);
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Free memory...
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
next = cert->next;
|
|
|
|
|
free(cert);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Certs = NULL;
|
|
|
|
|
RootCertTime = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* 'cupsdFindCert()' - Find a certificate.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
cupsd_cert_t * /* O - Matching certificate or NULL */
|
|
|
|
|
cupsdFindCert(const char *certificate) /* I - Certificate */
|
|
|
|
|
{
|
|
|
|
|
cupsd_cert_t *cert; /* Current certificate */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdFindCert(certificate=%s)", certificate);
|
|
|
|
|
for (cert = Certs; cert != NULL; cert = cert->next)
|
|
|
|
|
if (!ctcompare(certificate, cert->certificate))
|
|
|
|
|
{
|
|
|
|
|
cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdFindCert: Returning \"%s\".", cert->username);
|
|
|
|
|
return (cert);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdFindCert: Certificate not found.");
|
|
|
|
|
|
|
|
|
|
return (NULL);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* 'cupsdInitCerts()' - Initialize the certificate "system" and root
|
|
|
|
|
* certificate.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
cupsdInitCerts(void)
|
|
|
|
|
{
|
|
|
|
|
#ifndef HAVE_ARC4RANDOM
|
|
|
|
|
cups_file_t *fp; /* /dev/random file */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Initialize the random number generator using the random device or
|
|
|
|
|
* the current time, as available...
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
if ((fp = cupsFileOpen("/dev/urandom", "rb")) == NULL)
|
|
|
|
|
{
|
|
|
|
|
struct timeval tod; /* Time of day */
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Get the time in usecs and use it as the initial seed...
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
gettimeofday(&tod, NULL);
|
|
|
|
|
|
|
|
|
|
CUPS_SRAND((unsigned)(tod.tv_sec + tod.tv_usec));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
unsigned seed; /* Seed for random number generator */
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Read 4 random characters from the random device and use
|
|
|
|
|
* them as the seed...
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
seed = (unsigned)cupsFileGetChar(fp);
|
|
|
|
|
seed = (seed << 8) | (unsigned)cupsFileGetChar(fp);
|
|
|
|
|
seed = (seed << 8) | (unsigned)cupsFileGetChar(fp);
|
|
|
|
|
CUPS_SRAND((seed << 8) | (unsigned)cupsFileGetChar(fp));
|
|
|
|
|
|
|
|
|
|
cupsFileClose(fp);
|
|
|
|
|
}
|
|
|
|
|
#endif /* !HAVE_ARC4RANDOM */
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Create a root certificate and return...
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
if (!RunUser)
|
|
|
|
|
cupsdAddCert(0, "root", cupsdDefaultAuthType());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* 'ctcompare()' - Compare two strings in constant time.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
static int /* O - 0 on match, non-zero on non-match */
|
|
|
|
|
ctcompare(const char *a, /* I - First string */
|
|
|
|
|
const char *b) /* I - Second string */
|
|
|
|
|
{
|
|
|
|
|
int result = 0; /* Result */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
while (*a && *b)
|
|
|
|
|
{
|
|
|
|
|
result |= *a ^ *b;
|
|
|
|
|
a ++;
|
|
|
|
|
b ++;
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-12 15:27:08 +08:00
|
|
|
|
/*
|
|
|
|
|
* The while loop finishes when *a == '\0' or *b == '\0'
|
|
|
|
|
* so after the while loop either both *a and *b == '\0',
|
|
|
|
|
* or one points inside a string, so when we apply logical OR on *a,
|
|
|
|
|
* *b and result, we get a non-zero return value if the compared strings don't match.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
return (result | *a | *b);
|
2022-05-13 20:08:20 +08:00
|
|
|
|
}
|