Skip to content

Commit 449045b

Browse files
Implement a Socket::Credentials struct for handling unix socket creds
Depending on the platform, Unix sockets can support fetching the credentials of a remote peer, or pushing credentials to a remote peer, through a variety of socket options and ancillary message types. Currently, Ruby is able to parse these ancillary data and socket option structures, but the only way this is exposed back to Ruby code is through the #inspect method. Parsing inspect strings is not a robust way to write programs! This change adds a type Socket::Credentials, which is a structure for holding the various kinds of credentials that unixen support passing between processes. It also implements Socket::AncillaryData#credentials and Socket::Option#credentials, to allow parsing these credentials from ancillary data/socket options respectively.
1 parent b55da1c commit 449045b

File tree

9 files changed

+901
-128
lines changed

9 files changed

+901
-128
lines changed

ext/socket/ancdata.c

Lines changed: 56 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
static VALUE sym_wait_readable, sym_wait_writable;
66

77
#if defined(HAVE_STRUCT_MSGHDR_MSG_CONTROL)
8-
static VALUE rb_cAncillaryData;
8+
VALUE rb_cAncillaryData;
99

1010
static VALUE
1111
constant_to_sym(int constant, ID (*intern_const)(int))
@@ -270,6 +270,51 @@ ancillary_unix_rights(VALUE self)
270270
#define ancillary_unix_rights rb_f_notimplement
271271
#endif
272272

273+
/*
274+
* call-seq:
275+
* ancillarydata.credentials(local_creds_enabled: false, socket: nil) => Socket::Credentials instance
276+
*
277+
* Interprets the contents of this ancillary message as a Socket::Credentials
278+
* instance. If the ancillary data is not of type SCM_CREDS or SCM_CREDENTIALS,
279+
* an exception is raised, or if the data is malformed, an exception is raised.
280+
*
281+
* If _local_creds_enabled_ or _socket_ are passed in, they are used to resolve
282+
* any ambiguity in the format of the ancillary message data as described for
283+
* Socket::Credentials.from_ancillary_data
284+
*/
285+
static VALUE
286+
ancillary_credentials(int argc, VALUE *argv, VALUE self)
287+
{
288+
/* Annoyingly, we need to support passing local_creds_enabled: and
289+
* socket: kwargs through to Socket::Credentials::from_ancillary_data,
290+
* if present. It's kind of verbose to do that in C. */
291+
VALUE kwarg_hash = Qnil;
292+
ID kwarg_keys[2] = {
293+
rb_intern("local_creds_enabled"),
294+
rb_intern("socket"),
295+
};
296+
VALUE kwarg_values[2] = { Qundef, Qundef };
297+
298+
rb_scan_args(argc, argv, "0:", &kwarg_hash);
299+
if (RB_TEST(kwarg_hash)) {
300+
rb_get_kwargs(kwarg_hash, kwarg_keys, 0, 2, kwarg_values);
301+
}
302+
303+
VALUE passed_args[4];
304+
passed_args[0] = rb_attr_get(self, rb_intern("level"));
305+
passed_args[1] = rb_attr_get(self, rb_intern("type"));
306+
passed_args[2] = rb_attr_get(self, rb_intern("data"));
307+
passed_args[3] = RB_TEST(kwarg_hash) ? kwarg_hash : rb_hash_new();
308+
309+
VALUE r = rb_funcallv_kw(rb_cSocketCredentials, rb_intern("from_ancillary_data"),
310+
4, passed_args, 1);
311+
if (!RB_TEST(r)) {
312+
rb_raise(rb_eTypeError,
313+
"SCM_CREDS or SCM_CREDENTIALS ancillary data was malformed");
314+
}
315+
return r;
316+
}
317+
273318
#if defined(SCM_TIMESTAMP) || defined(SCM_TIMESTAMPNS) || defined(SCM_BINTIME)
274319
/*
275320
* call-seq:
@@ -688,88 +733,19 @@ anc_inspect_socket_rights(int level, int type, VALUE data, VALUE ret)
688733
}
689734
#endif
690735

691-
#if defined(SCM_CREDENTIALS) /* GNU/Linux */
692-
static int
693-
anc_inspect_passcred_credentials(int level, int type, VALUE data, VALUE ret)
694-
{
695-
if (level == SOL_SOCKET && type == SCM_CREDENTIALS &&
696-
RSTRING_LEN(data) == sizeof(struct ucred)) {
697-
struct ucred cred;
698-
memcpy(&cred, RSTRING_PTR(data), sizeof(struct ucred));
699-
rb_str_catf(ret, " pid=%u uid=%u gid=%u", cred.pid, cred.uid, cred.gid);
700-
rb_str_cat2(ret, " (ucred)");
701-
return 1;
702-
}
703-
else {
704-
return 0;
705-
}
706-
}
707-
#endif
708736

709-
#if defined(SCM_CREDS)
710-
#define INSPECT_SCM_CREDS
737+
#if defined(SCM_CREDS) || defined(SCM_CREDENTIALS)
711738
static int
712739
anc_inspect_socket_creds(int level, int type, VALUE data, VALUE ret)
713740
{
714-
if (level != SOL_SOCKET && type != SCM_CREDS)
741+
VALUE cr = rb_funcall(rb_cSocketCredentials, rb_intern("from_ancillary_data"), 3,
742+
RB_INT2NUM(level), RB_INT2NUM(type), data);
743+
if (!RB_TEST(cr)) {
715744
return 0;
716-
717-
/*
718-
* FreeBSD has struct cmsgcred and struct sockcred.
719-
* They use both SOL_SOCKET/SCM_CREDS in the ancillary message.
720-
* They are not ambiguous from the view of the caller
721-
* because struct sockcred is sent if and only if the caller sets LOCAL_CREDS socket option.
722-
* But inspect method doesn't know it.
723-
* So they are ambiguous from the view of inspect.
724-
* This function distinguish them by the size of the ancillary message.
725-
* This heuristics works well except when sc_ngroups == CMGROUP_MAX.
726-
*/
727-
728-
#if defined(HAVE_TYPE_STRUCT_CMSGCRED) /* FreeBSD */
729-
if (RSTRING_LEN(data) == sizeof(struct cmsgcred)) {
730-
struct cmsgcred cred;
731-
memcpy(&cred, RSTRING_PTR(data), sizeof(struct cmsgcred));
732-
rb_str_catf(ret, " pid=%u", cred.cmcred_pid);
733-
rb_str_catf(ret, " uid=%u", cred.cmcred_uid);
734-
rb_str_catf(ret, " euid=%u", cred.cmcred_euid);
735-
rb_str_catf(ret, " gid=%u", cred.cmcred_gid);
736-
if (cred.cmcred_ngroups) {
737-
int i;
738-
const char *sep = " groups=";
739-
for (i = 0; i < cred.cmcred_ngroups; i++) {
740-
rb_str_catf(ret, "%s%u", sep, cred.cmcred_groups[i]);
741-
sep = ",";
742-
}
743-
}
744-
rb_str_cat2(ret, " (cmsgcred)");
745-
return 1;
746-
}
747-
#endif
748-
#if defined(HAVE_TYPE_STRUCT_SOCKCRED) /* FreeBSD, NetBSD */
749-
if ((size_t)RSTRING_LEN(data) >= SOCKCREDSIZE(0)) {
750-
struct sockcred cred0, *cred;
751-
memcpy(&cred0, RSTRING_PTR(data), SOCKCREDSIZE(0));
752-
if ((size_t)RSTRING_LEN(data) == SOCKCREDSIZE(cred0.sc_ngroups)) {
753-
cred = (struct sockcred *)ALLOCA_N(char, SOCKCREDSIZE(cred0.sc_ngroups));
754-
memcpy(cred, RSTRING_PTR(data), SOCKCREDSIZE(cred0.sc_ngroups));
755-
rb_str_catf(ret, " uid=%u", cred->sc_uid);
756-
rb_str_catf(ret, " euid=%u", cred->sc_euid);
757-
rb_str_catf(ret, " gid=%u", cred->sc_gid);
758-
rb_str_catf(ret, " egid=%u", cred->sc_egid);
759-
if (cred0.sc_ngroups) {
760-
int i;
761-
const char *sep = " groups=";
762-
for (i = 0; i < cred0.sc_ngroups; i++) {
763-
rb_str_catf(ret, "%s%u", sep, cred->sc_groups[i]);
764-
sep = ",";
765-
}
766-
}
767-
rb_str_cat2(ret, " (sockcred)");
768-
return 1;
769-
}
770745
}
771-
#endif
772-
return 0;
746+
VALUE fragment = rsock_credentials_inspect_fragment(cr);
747+
rb_str_append(ret, fragment);
748+
return 1;
773749
}
774750
#endif
775751

@@ -1030,9 +1006,9 @@ ancillary_inspect(VALUE self)
10301006
case SCM_RIGHTS: inspected = anc_inspect_socket_rights(level, type, data, ret); break;
10311007
# endif
10321008
# if defined(SCM_CREDENTIALS) /* GNU/Linux */
1033-
case SCM_CREDENTIALS: inspected = anc_inspect_passcred_credentials(level, type, data, ret); break;
1009+
case SCM_CREDENTIALS: inspected = anc_inspect_socket_creds(level, type, data, ret); break;
10341010
# endif
1035-
# if defined(INSPECT_SCM_CREDS) /* NetBSD */
1011+
# if defined(SCM_CREDS) /* FreeBSD, NetBSD */
10361012
case SCM_CREDS: inspected = anc_inspect_socket_creds(level, type, data, ret); break;
10371013
# endif
10381014
}
@@ -1721,6 +1697,7 @@ rsock_init_ancdata(void)
17211697

17221698
rb_define_singleton_method(rb_cAncillaryData, "unix_rights", ancillary_s_unix_rights, -1);
17231699
rb_define_method(rb_cAncillaryData, "unix_rights", ancillary_unix_rights, 0);
1700+
rb_define_method(rb_cAncillaryData, "credentials", ancillary_credentials, -1);
17241701

17251702
rb_define_method(rb_cAncillaryData, "timestamp", ancillary_timestamp, 0);
17261703

0 commit comments

Comments
 (0)