/*
* convert a presentation address string to a sockaddr_storage struct. Returns
- * true on success and false on failure.
+ * true on success or false on failure.
*
* Note that we do not populate the sin6_scope_id field here for IPv6 addrs.
* gssd nececessarily relies on hostname resolution and DNS AAAA records
* not really feasible at present.
*/
static int
-addrstr_to_sockaddr(struct sockaddr *sa, const char *addr, const int port)
+addrstr_to_sockaddr(struct sockaddr *sa, const char *node, const char *port)
{
- struct sockaddr_in *s4 = (struct sockaddr_in *) sa;
-#ifdef IPV6_SUPPORTED
- struct sockaddr_in6 *s6 = (struct sockaddr_in6 *) sa;
-#endif /* IPV6_SUPPORTED */
+ int rc;
+ struct addrinfo *res;
+ struct addrinfo hints = { .ai_flags = AI_NUMERICHOST | AI_NUMERICSERV };
- if (inet_pton(AF_INET, addr, &s4->sin_addr)) {
- s4->sin_family = AF_INET;
- s4->sin_port = htons(port);
-#ifdef IPV6_SUPPORTED
- } else if (inet_pton(AF_INET6, addr, &s6->sin6_addr)) {
- s6->sin6_family = AF_INET6;
- s6->sin6_port = htons(port);
+#ifndef IPV6_SUPPORTED
+ hints.ai_family = AF_INET;
#endif /* IPV6_SUPPORTED */
- } else {
- printerr(0, "ERROR: unable to convert %s to address\n", addr);
+
+ rc = getaddrinfo(node, port, &hints, &res);
+ if (rc) {
+ printerr(0, "ERROR: unable to convert %s|%s to sockaddr: %s\n",
+ node, port, rc == EAI_SYSTEM ? strerror(errno) :
+ gai_strerror(rc));
return 0;
}
+#ifdef IPV6_SUPPORTED
+ /*
+ * getnameinfo ignores the scopeid. If the address turns out to have
+ * a non-zero scopeid, we can't use it -- the resolved host might be
+ * completely different from the one intended.
+ */
+ if (res->ai_addr->sa_family == AF_INET6) {
+ struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)res->ai_addr;
+ if (sin6->sin6_scope_id) {
+ printerr(0, "ERROR: address %s has non-zero "
+ "sin6_scope_id!\n", node);
+ freeaddrinfo(res);
+ return 0;
+ }
+ }
+#endif /* IPV6_SUPPORTED */
+
+ memcpy(sa, res->ai_addr, res->ai_addrlen);
+ freeaddrinfo(res);
return 1;
}
char program[16];
char version[16];
char protoname[16];
- char cb_port[128];
+ char port[128];
char *p;
int fd = -1;
int numfields;
- int port = 0;
*servicename = *servername = *protocol = NULL;
goto fail;
}
- cb_port[0] = '\0';
+ port[0] = '\0';
if ((p = strstr(buf, "port")) != NULL)
- sscanf(p, "port: %127s\n", cb_port);
+ sscanf(p, "port: %127s\n", port);
/* check service, program, and version */
if (memcmp(service, "nfs", 3) != 0)
goto fail;
}
- if (cb_port[0] != '\0') {
- port = atoi(cb_port);
- if (port < 0 || port > 65535)
- goto fail;
- }
-
if (!addrstr_to_sockaddr(addr, address, port))
goto fail;
return retval;
}
+/* Encryption types supported by the kernel rpcsec_gss code */
+int num_krb5_enctypes = 0;
+krb5_enctype *krb5_enctypes = NULL;
+
+/*
+ * Parse the supported encryption type information
+ */
+static int
+parse_enctypes(char *enctypes)
+{
+ int n = 0;
+ char *curr, *comma;
+ int i;
+ static char *cached_types;
+
+ if (cached_types && strcmp(cached_types, enctypes) == 0)
+ return 0;
+ free(cached_types);
+
+ if (krb5_enctypes != NULL) {
+ free(krb5_enctypes);
+ krb5_enctypes = NULL;
+ num_krb5_enctypes = 0;
+ }
+
+ /* count the number of commas */
+ for (curr = enctypes; curr && *curr != '\0'; curr = ++comma) {
+ comma = strchr(curr, ',');
+ if (comma != NULL)
+ n++;
+ else
+ break;
+ }
+ /* If no more commas and we're not at the end, there's one more value */
+ if (*curr != '\0')
+ n++;
+
+ /* Empty string, return an error */
+ if (n == 0)
+ return ENOENT;
+
+ /* Allocate space for enctypes array */
+ if ((krb5_enctypes = (int *) calloc(n, sizeof(int))) == NULL) {
+ return ENOMEM;
+ }
+
+ /* Now parse each value into the array */
+ for (curr = enctypes, i = 0; curr && *curr != '\0'; curr = ++comma) {
+ krb5_enctypes[i++] = atoi(curr);
+ comma = strchr(curr, ',');
+ if (comma == NULL)
+ break;
+ }
+
+ num_krb5_enctypes = n;
+ if ((cached_types = malloc(strlen(enctypes)+1)))
+ strcpy(cached_types, enctypes);
+
+ return 0;
+}
+
static int
do_downcall(int k5_fd, uid_t uid, struct authgss_private_data *pd,
gss_buffer_desc *context_token)
* Do this before creating rpc connection since we won't need
* rpc connection if it fails!
*/
- if (limit_krb5_enctypes(&sec, uid)) {
+ if (limit_krb5_enctypes(&sec)) {
printerr(1, "WARNING: Failed while limiting krb5 "
"encryption types for user with uid %d\n",
uid);
if (sec.cred != GSS_C_NO_CREDENTIAL)
gss_release_cred(&min_stat, &sec.cred);
/* Restore euid to original value */
- if ((save_uid != -1) && (setfsuid(save_uid) != uid)) {
+ if (((int)save_uid != -1) && (setfsuid(save_uid) != (int)uid)) {
printerr(0, "WARNING: Failed to restore fsuid"
" to uid %d from %d\n", save_uid, uid);
}
* context on behalf of the kernel
*/
static void
-process_krb5_upcall(struct clnt_info *clp, uid_t uid, int fd, char *tgtname)
+process_krb5_upcall(struct clnt_info *clp, uid_t uid, int fd, char *tgtname,
+ char *service)
{
CLIENT *rpc_clnt = NULL;
AUTH *auth = NULL;
char **ccname;
char **dirname;
int create_resp = -1;
+ int err, downcall_err = -EACCES;
printerr(1, "handling krb5 upcall (%s)\n", clp->dirname);
token.value = NULL;
memset(&pd, 0, sizeof(struct authgss_private_data));
- if (uid != 0 || (uid == 0 && root_uses_machine_creds == 0)) {
+ /*
+ * If "service" is specified, then the kernel is indicating that
+ * we must use machine credentials for this request. (Regardless
+ * of the uid value or the setting of root_uses_machine_creds.)
+ * If the service value is "*", then any service name can be used.
+ * Otherwise, it specifies the service name that should be used.
+ * (For now, the values of service will only be "*" or "nfs".)
+ *
+ * Restricting gssd to use "nfs" service name is needed for when
+ * the NFS server is doing a callback to the NFS client. In this
+ * case, the NFS server has to authenticate itself as "nfs" --
+ * even if there are other service keys such as "host" or "root"
+ * in the keytab.
+ *
+ * Another case when the kernel may specify the service attribute
+ * is when gssd is being asked to create the context for a
+ * SETCLIENT_ID operation. In this case, machine credentials
+ * must be used for the authentication. However, the service name
+ * used for this case is not important.
+ *
+ */
+ printerr(2, "%s: service is '%s'\n", __func__,
+ service ? service : "<null>");
+ if (uid != 0 || (uid == 0 && root_uses_machine_creds == 0 &&
+ service == NULL)) {
/* Tell krb5 gss which credentials cache to use */
for (dirname = ccachesearch; *dirname != NULL; dirname++) {
- if (gssd_setup_krb5_user_gss_ccache(uid, clp->servername, *dirname) == 0)
+ err = gssd_setup_krb5_user_gss_ccache(uid, clp->servername, *dirname);
+ if (err == -EKEYEXPIRED)
+ downcall_err = -EKEYEXPIRED;
+ else if (!err)
create_resp = create_auth_rpc_client(clp, &rpc_clnt, &auth, uid,
AUTHTYPE_KRB5);
if (create_resp == 0)
}
}
if (create_resp != 0) {
- if (uid == 0 && root_uses_machine_creds == 1) {
+ if (uid == 0 && (root_uses_machine_creds == 1 ||
+ service != NULL)) {
int nocache = 0;
int success = 0;
do {
gssd_refresh_krb5_machine_credential(clp->servername,
- NULL, nocache);
+ NULL, service);
/*
* Get a list of credential cache names and try each
* of them until one works or we've tried them all
return;
out_return_error:
- do_error_downcall(fd, uid, -1);
+ do_error_downcall(fd, uid, downcall_err);
goto out;
}
{
uid_t uid;
- if (read(clp->krb5_fd, &uid, sizeof(uid)) < sizeof(uid)) {
+ if (read(clp->krb5_fd, &uid, sizeof(uid)) < (ssize_t)sizeof(uid)) {
printerr(0, "WARNING: failed reading uid from krb5 "
"upcall pipe: %s\n", strerror(errno));
return;
}
- return process_krb5_upcall(clp, uid, clp->krb5_fd, NULL);
+ return process_krb5_upcall(clp, uid, clp->krb5_fd, NULL, NULL);
}
void
{
uid_t uid;
- if (read(clp->spkm3_fd, &uid, sizeof(uid)) < sizeof(uid)) {
+ if (read(clp->spkm3_fd, &uid, sizeof(uid)) < (ssize_t)sizeof(uid)) {
printerr(0, "WARNING: failed reading uid from spkm3 "
"upcall pipe: %s\n", strerror(errno));
return;
char *p;
char *mech = NULL;
char *target = NULL;
+ char *service = NULL;
+ char *enctypes = NULL;
printerr(1, "handling gssd upcall (%s)\n", clp->dirname);
goto out;
}
+ /* read supported encryption types if supplied */
+ if ((p = strstr(lbuf, "enctypes=")) != NULL) {
+ enctypes = malloc(lbuflen);
+ if (!enctypes)
+ goto out;
+ if (sscanf(p, "enctypes=%s", enctypes) != 1) {
+ printerr(0, "WARNING: handle_gssd_upcall: "
+ "failed to parse encryption types "
+ "in upcall string '%s'\n", lbuf);
+ goto out;
+ }
+ if (parse_enctypes(enctypes) != 0) {
+ printerr(0, "WARNING: handle_gssd_upcall: "
+ "parsing encryption types failed: errno %d\n", errno);
+ }
+ }
+
/* read target name */
if ((p = strstr(lbuf, "target=")) != NULL) {
target = malloc(lbuflen);
}
}
+ /*
+ * read the service name
+ *
+ * The presence of attribute "service=" indicates that machine
+ * credentials should be used for this request. If the value
+ * is "*", then any machine credentials available can be used.
+ * If the value is anything else, then machine credentials for
+ * the specified service name (always "nfs" for now) should be
+ * used.
+ */
+ if ((p = strstr(lbuf, "service=")) != NULL) {
+ service = malloc(lbuflen);
+ if (!service)
+ goto out;
+ if (sscanf(p, "service=%s", service) != 1) {
+ printerr(0, "WARNING: handle_gssd_upcall: "
+ "failed to parse service type "
+ "in upcall string '%s'\n", lbuf);
+ goto out;
+ }
+ }
+
if (strcmp(mech, "krb5") == 0)
- process_krb5_upcall(clp, uid, clp->gssd_fd, target);
+ process_krb5_upcall(clp, uid, clp->gssd_fd, target, service);
else if (strcmp(mech, "spkm3") == 0)
process_spkm3_upcall(clp, uid, clp->gssd_fd);
else
out:
free(lbuf);
free(mech);
+ free(enctypes);
free(target);
+ free(service);
return;
}