blob: fe82fabc31a408a41c42f8f9ada555506fa4e367 [file] [log] [blame]
Julien Boeuf9f218dd2015-04-23 10:24:02 -07001/*
2 *
Craig Tiller6169d5f2016-03-31 07:46:18 -07003 * Copyright 2015, Google Inc.
Julien Boeuf9f218dd2015-04-23 10:24:02 -07004 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are
8 * met:
9 *
10 * * Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * * Redistributions in binary form must reproduce the above
13 * copyright notice, this list of conditions and the following disclaimer
14 * in the documentation and/or other materials provided with the
15 * distribution.
16 * * Neither the name of Google Inc. nor the names of its
17 * contributors may be used to endorse or promote products derived from
18 * this software without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 *
32 */
33
34#include <string.h>
35
Julien Boeuf8ca294e2016-05-02 14:56:30 -070036#include "src/core/lib/security/context/security_context.h"
Craig Tiller9533d042016-03-25 17:11:06 -070037#include "src/core/lib/support/string.h"
38#include "src/core/lib/surface/api_trace.h"
39#include "src/core/lib/surface/call.h"
Julien Boeuf9f218dd2015-04-23 10:24:02 -070040
41#include <grpc/grpc_security.h>
42#include <grpc/support/alloc.h>
43#include <grpc/support/log.h>
Masood Malekghassemi701af602015-06-03 15:01:17 -070044#include <grpc/support/string_util.h>
Julien Boeuf9f218dd2015-04-23 10:24:02 -070045
Julien Boeuf84d964a2015-04-29 11:31:06 -070046/* --- grpc_call --- */
47
Julien Boeuf9f218dd2015-04-23 10:24:02 -070048grpc_call_error grpc_call_set_credentials(grpc_call *call,
Julien Boeuf441176d2015-10-09 21:14:07 -070049 grpc_call_credentials *creds) {
Craig Tillerbd1795c2016-10-31 15:30:00 -070050 grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT;
Julien Boeuf9f218dd2015-04-23 10:24:02 -070051 grpc_client_security_context *ctx = NULL;
Masood Malekghassemi76c3d742015-08-19 18:22:53 -070052 GRPC_API_TRACE("grpc_call_set_credentials(call=%p, creds=%p)", 2,
53 (call, creds));
Julien Boeuf9f218dd2015-04-23 10:24:02 -070054 if (!grpc_call_is_client(call)) {
55 gpr_log(GPR_ERROR, "Method is client-side only.");
56 return GRPC_CALL_ERROR_NOT_ON_SERVER;
57 }
Julien Boeuf9f218dd2015-04-23 10:24:02 -070058 ctx = (grpc_client_security_context *)grpc_call_context_get(
59 call, GRPC_CONTEXT_SECURITY);
60 if (ctx == NULL) {
61 ctx = grpc_client_security_context_create();
Julien Boeuf441176d2015-10-09 21:14:07 -070062 ctx->creds = grpc_call_credentials_ref(creds);
Julien Boeuf9f218dd2015-04-23 10:24:02 -070063 grpc_call_context_set(call, GRPC_CONTEXT_SECURITY, ctx,
64 grpc_client_security_context_destroy);
65 } else {
Craig Tillerbd1795c2016-10-31 15:30:00 -070066 grpc_call_credentials_unref(&exec_ctx, ctx->creds);
Julien Boeuf441176d2015-10-09 21:14:07 -070067 ctx->creds = grpc_call_credentials_ref(creds);
Julien Boeuf9f218dd2015-04-23 10:24:02 -070068 }
Craig Tillerbd1795c2016-10-31 15:30:00 -070069 grpc_exec_ctx_finish(&exec_ctx);
Julien Boeuf9f218dd2015-04-23 10:24:02 -070070 return GRPC_CALL_OK;
71}
72
yang-gf9e8e592015-07-09 12:32:15 -070073grpc_auth_context *grpc_call_auth_context(grpc_call *call) {
Julien Boeuf84d964a2015-04-29 11:31:06 -070074 void *sec_ctx = grpc_call_context_get(call, GRPC_CONTEXT_SECURITY);
Masood Malekghassemi76c3d742015-08-19 18:22:53 -070075 GRPC_API_TRACE("grpc_call_auth_context(call=%p)", 1, (call));
Julien Boeuf84d964a2015-04-29 11:31:06 -070076 if (sec_ctx == NULL) return NULL;
77 return grpc_call_is_client(call)
yang-gf9e8e592015-07-09 12:32:15 -070078 ? GRPC_AUTH_CONTEXT_REF(
79 ((grpc_client_security_context *)sec_ctx)->auth_context,
80 "grpc_call_auth_context client")
81 : GRPC_AUTH_CONTEXT_REF(
82 ((grpc_server_security_context *)sec_ctx)->auth_context,
83 "grpc_call_auth_context server");
84}
85
86void grpc_auth_context_release(grpc_auth_context *context) {
Masood Malekghassemi76c3d742015-08-19 18:22:53 -070087 GRPC_API_TRACE("grpc_auth_context_release(context=%p)", 1, (context));
yang-gf9e8e592015-07-09 12:32:15 -070088 GRPC_AUTH_CONTEXT_UNREF(context, "grpc_auth_context_unref");
Julien Boeuf84d964a2015-04-29 11:31:06 -070089}
90
91/* --- grpc_client_security_context --- */
92
Julien Boeuf9f218dd2015-04-23 10:24:02 -070093grpc_client_security_context *grpc_client_security_context_create(void) {
94 grpc_client_security_context *ctx =
95 gpr_malloc(sizeof(grpc_client_security_context));
96 memset(ctx, 0, sizeof(grpc_client_security_context));
97 return ctx;
98}
99
100void grpc_client_security_context_destroy(void *ctx) {
Craig Tillerbd1795c2016-10-31 15:30:00 -0700101 grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT;
Julien Boeuf9f218dd2015-04-23 10:24:02 -0700102 grpc_client_security_context *c = (grpc_client_security_context *)ctx;
Craig Tillerbd1795c2016-10-31 15:30:00 -0700103 grpc_call_credentials_unref(&exec_ctx, c->creds);
Craig Tiller991edad2015-06-30 11:40:41 -0700104 GRPC_AUTH_CONTEXT_UNREF(c->auth_context, "client_security_context");
Julien Boeuf6b93d462016-08-23 10:04:03 -0700105 if (c->extension.instance != NULL && c->extension.destroy != NULL) {
106 c->extension.destroy(c->extension.instance);
107 }
Julien Boeuf9f218dd2015-04-23 10:24:02 -0700108 gpr_free(ctx);
Craig Tillerbd1795c2016-10-31 15:30:00 -0700109 grpc_exec_ctx_finish(&exec_ctx);
Julien Boeuf9f218dd2015-04-23 10:24:02 -0700110}
Julien Boeuf84d964a2015-04-29 11:31:06 -0700111
112/* --- grpc_server_security_context --- */
113
114grpc_server_security_context *grpc_server_security_context_create(void) {
115 grpc_server_security_context *ctx =
116 gpr_malloc(sizeof(grpc_server_security_context));
117 memset(ctx, 0, sizeof(grpc_server_security_context));
118 return ctx;
119}
120
121void grpc_server_security_context_destroy(void *ctx) {
122 grpc_server_security_context *c = (grpc_server_security_context *)ctx;
Craig Tiller991edad2015-06-30 11:40:41 -0700123 GRPC_AUTH_CONTEXT_UNREF(c->auth_context, "server_security_context");
Julien Boeuf6b93d462016-08-23 10:04:03 -0700124 if (c->extension.instance != NULL && c->extension.destroy != NULL) {
125 c->extension.destroy(c->extension.instance);
126 }
Julien Boeuf84d964a2015-04-29 11:31:06 -0700127 gpr_free(ctx);
128}
129
130/* --- grpc_auth_context --- */
131
Julien Boeuf83b02972015-05-20 22:50:34 -0700132static grpc_auth_property_iterator empty_iterator = {NULL, 0, NULL};
133
Julien Boeufea456fc2015-07-07 15:23:30 -0700134grpc_auth_context *grpc_auth_context_create(grpc_auth_context *chained) {
Julien Boeuf84d964a2015-04-29 11:31:06 -0700135 grpc_auth_context *ctx = gpr_malloc(sizeof(grpc_auth_context));
136 memset(ctx, 0, sizeof(grpc_auth_context));
Julien Boeuf84d964a2015-04-29 11:31:06 -0700137 gpr_ref_init(&ctx->refcount, 1);
Julien Boeufea456fc2015-07-07 15:23:30 -0700138 if (chained != NULL) {
139 ctx->chained = GRPC_AUTH_CONTEXT_REF(chained, "chained");
140 ctx->peer_identity_property_name =
141 ctx->chained->peer_identity_property_name;
142 }
Julien Boeuf84d964a2015-04-29 11:31:06 -0700143 return ctx;
144}
145
Craig Tiller991edad2015-06-30 11:40:41 -0700146#ifdef GRPC_AUTH_CONTEXT_REFCOUNT_DEBUG
147grpc_auth_context *grpc_auth_context_ref(grpc_auth_context *ctx,
148 const char *file, int line,
149 const char *reason) {
150 if (ctx == NULL) return NULL;
151 gpr_log(file, line, GPR_LOG_SEVERITY_DEBUG,
152 "AUTH_CONTEXT:%p ref %d -> %d %s", ctx, (int)ctx->refcount.count,
153 (int)ctx->refcount.count + 1, reason);
154#else
Julien Boeuf84d964a2015-04-29 11:31:06 -0700155grpc_auth_context *grpc_auth_context_ref(grpc_auth_context *ctx) {
156 if (ctx == NULL) return NULL;
Craig Tiller991edad2015-06-30 11:40:41 -0700157#endif
Julien Boeuf84d964a2015-04-29 11:31:06 -0700158 gpr_ref(&ctx->refcount);
159 return ctx;
160}
161
Craig Tiller991edad2015-06-30 11:40:41 -0700162#ifdef GRPC_AUTH_CONTEXT_REFCOUNT_DEBUG
163void grpc_auth_context_unref(grpc_auth_context *ctx, const char *file, int line,
164 const char *reason) {
165 if (ctx == NULL) return;
166 gpr_log(file, line, GPR_LOG_SEVERITY_DEBUG,
167 "AUTH_CONTEXT:%p unref %d -> %d %s", ctx, (int)ctx->refcount.count,
168 (int)ctx->refcount.count - 1, reason);
169#else
Julien Boeuf84d964a2015-04-29 11:31:06 -0700170void grpc_auth_context_unref(grpc_auth_context *ctx) {
171 if (ctx == NULL) return;
Craig Tiller991edad2015-06-30 11:40:41 -0700172#endif
Julien Boeuf84d964a2015-04-29 11:31:06 -0700173 if (gpr_unref(&ctx->refcount)) {
174 size_t i;
Craig Tiller991edad2015-06-30 11:40:41 -0700175 GRPC_AUTH_CONTEXT_UNREF(ctx->chained, "chained");
Julien Boeufea456fc2015-07-07 15:23:30 -0700176 if (ctx->properties.array != NULL) {
177 for (i = 0; i < ctx->properties.count; i++) {
178 grpc_auth_property_reset(&ctx->properties.array[i]);
Julien Boeuf84d964a2015-04-29 11:31:06 -0700179 }
Julien Boeufea456fc2015-07-07 15:23:30 -0700180 gpr_free(ctx->properties.array);
Julien Boeuf84d964a2015-04-29 11:31:06 -0700181 }
Julien Boeuf83b02972015-05-20 22:50:34 -0700182 gpr_free(ctx);
Julien Boeuf84d964a2015-04-29 11:31:06 -0700183 }
184}
185
186const char *grpc_auth_context_peer_identity_property_name(
187 const grpc_auth_context *ctx) {
Masood Malekghassemi76c3d742015-08-19 18:22:53 -0700188 GRPC_API_TRACE("grpc_auth_context_peer_identity_property_name(ctx=%p)", 1,
189 (ctx));
Julien Boeuf84d964a2015-04-29 11:31:06 -0700190 return ctx->peer_identity_property_name;
191}
192
Julien Boeufea456fc2015-07-07 15:23:30 -0700193int grpc_auth_context_set_peer_identity_property_name(grpc_auth_context *ctx,
194 const char *name) {
195 grpc_auth_property_iterator it =
196 grpc_auth_context_find_properties_by_name(ctx, name);
197 const grpc_auth_property *prop = grpc_auth_property_iterator_next(&it);
Masood Malekghassemi76c3d742015-08-19 18:22:53 -0700198 GRPC_API_TRACE(
199 "grpc_auth_context_set_peer_identity_property_name(ctx=%p, name=%s)", 2,
200 (ctx, name));
Julien Boeufea456fc2015-07-07 15:23:30 -0700201 if (prop == NULL) {
202 gpr_log(GPR_ERROR, "Property name %s not found in auth context.",
203 name != NULL ? name : "NULL");
204 return 0;
205 }
206 ctx->peer_identity_property_name = prop->name;
207 return 1;
208}
209
Craig Tillerd6c98df2015-08-18 09:33:44 -0700210int grpc_auth_context_peer_is_authenticated(const grpc_auth_context *ctx) {
Masood Malekghassemi76c3d742015-08-19 18:22:53 -0700211 GRPC_API_TRACE("grpc_auth_context_peer_is_authenticated(ctx=%p)", 1, (ctx));
Julien Boeuf83b02972015-05-20 22:50:34 -0700212 return ctx->peer_identity_property_name == NULL ? 0 : 1;
213}
214
215grpc_auth_property_iterator grpc_auth_context_property_iterator(
216 const grpc_auth_context *ctx) {
217 grpc_auth_property_iterator it = empty_iterator;
Masood Malekghassemi76c3d742015-08-19 18:22:53 -0700218 GRPC_API_TRACE("grpc_auth_context_property_iterator(ctx=%p)", 1, (ctx));
Julien Boeuf83b02972015-05-20 22:50:34 -0700219 if (ctx == NULL) return it;
220 it.ctx = ctx;
Julien Boeuf84d964a2015-04-29 11:31:06 -0700221 return it;
222}
223
224const grpc_auth_property *grpc_auth_property_iterator_next(
225 grpc_auth_property_iterator *it) {
Masood Malekghassemi76c3d742015-08-19 18:22:53 -0700226 GRPC_API_TRACE("grpc_auth_property_iterator_next(it=%p)", 1, (it));
Julien Boeuf83b02972015-05-20 22:50:34 -0700227 if (it == NULL || it->ctx == NULL) return NULL;
Julien Boeufea456fc2015-07-07 15:23:30 -0700228 while (it->index == it->ctx->properties.count) {
Julien Boeuf84d964a2015-04-29 11:31:06 -0700229 if (it->ctx->chained == NULL) return NULL;
230 it->ctx = it->ctx->chained;
231 it->index = 0;
232 }
233 if (it->name == NULL) {
Julien Boeufea456fc2015-07-07 15:23:30 -0700234 return &it->ctx->properties.array[it->index++];
Julien Boeuf84d964a2015-04-29 11:31:06 -0700235 } else {
Julien Boeufea456fc2015-07-07 15:23:30 -0700236 while (it->index < it->ctx->properties.count) {
237 const grpc_auth_property *prop = &it->ctx->properties.array[it->index++];
Julien Boeuf84d964a2015-04-29 11:31:06 -0700238 GPR_ASSERT(prop->name != NULL);
239 if (strcmp(it->name, prop->name) == 0) {
240 return prop;
241 }
242 }
243 /* We could not find the name, try another round. */
244 return grpc_auth_property_iterator_next(it);
245 }
246}
247
Julien Boeuf83b02972015-05-20 22:50:34 -0700248grpc_auth_property_iterator grpc_auth_context_find_properties_by_name(
Julien Boeuf84d964a2015-04-29 11:31:06 -0700249 const grpc_auth_context *ctx, const char *name) {
Julien Boeuf83b02972015-05-20 22:50:34 -0700250 grpc_auth_property_iterator it = empty_iterator;
Masood Malekghassemi76c3d742015-08-19 18:22:53 -0700251 GRPC_API_TRACE("grpc_auth_context_find_properties_by_name(ctx=%p, name=%s)",
252 2, (ctx, name));
Julien Boeuf83b02972015-05-20 22:50:34 -0700253 if (ctx == NULL || name == NULL) return empty_iterator;
254 it.ctx = ctx;
255 it.name = name;
Julien Boeuf84d964a2015-04-29 11:31:06 -0700256 return it;
257}
258
Julien Boeuf83b02972015-05-20 22:50:34 -0700259grpc_auth_property_iterator grpc_auth_context_peer_identity(
Julien Boeuf84d964a2015-04-29 11:31:06 -0700260 const grpc_auth_context *ctx) {
Masood Malekghassemi76c3d742015-08-19 18:22:53 -0700261 GRPC_API_TRACE("grpc_auth_context_peer_identity(ctx=%p)", 1, (ctx));
Julien Boeuf83b02972015-05-20 22:50:34 -0700262 if (ctx == NULL) return empty_iterator;
Julien Boeuf84d964a2015-04-29 11:31:06 -0700263 return grpc_auth_context_find_properties_by_name(
264 ctx, ctx->peer_identity_property_name);
265}
266
Julien Boeufea456fc2015-07-07 15:23:30 -0700267static void ensure_auth_context_capacity(grpc_auth_context *ctx) {
268 if (ctx->properties.count == ctx->properties.capacity) {
269 ctx->properties.capacity =
270 GPR_MAX(ctx->properties.capacity + 8, ctx->properties.capacity * 2);
271 ctx->properties.array =
272 gpr_realloc(ctx->properties.array,
273 ctx->properties.capacity * sizeof(grpc_auth_property));
274 }
Julien Boeuf84d964a2015-04-29 11:31:06 -0700275}
276
Julien Boeufea456fc2015-07-07 15:23:30 -0700277void grpc_auth_context_add_property(grpc_auth_context *ctx, const char *name,
278 const char *value, size_t value_length) {
279 grpc_auth_property *prop;
Masood Malekghassemi76c3d742015-08-19 18:22:53 -0700280 GRPC_API_TRACE(
281 "grpc_auth_context_add_property(ctx=%p, name=%s, value=%*.*s, "
Craig Tiller4de3e4f2015-10-05 08:55:50 -0700282 "value_length=%lu)",
Masood Malekghassemi76c3d742015-08-19 18:22:53 -0700283 6, (ctx, name, (int)value_length, (int)value_length, value,
284 (unsigned long)value_length));
Julien Boeufea456fc2015-07-07 15:23:30 -0700285 ensure_auth_context_capacity(ctx);
286 prop = &ctx->properties.array[ctx->properties.count++];
287 prop->name = gpr_strdup(name);
288 prop->value = gpr_malloc(value_length + 1);
289 memcpy(prop->value, value, value_length);
290 prop->value[value_length] = '\0';
291 prop->value_length = value_length;
292}
293
294void grpc_auth_context_add_cstring_property(grpc_auth_context *ctx,
295 const char *name,
296 const char *value) {
297 grpc_auth_property *prop;
Masood Malekghassemi76c3d742015-08-19 18:22:53 -0700298 GRPC_API_TRACE(
299 "grpc_auth_context_add_cstring_property(ctx=%p, name=%s, value=%s)", 3,
300 (ctx, name, value));
Julien Boeufea456fc2015-07-07 15:23:30 -0700301 ensure_auth_context_capacity(ctx);
302 prop = &ctx->properties.array[ctx->properties.count++];
303 prop->name = gpr_strdup(name);
304 prop->value = gpr_strdup(value);
305 prop->value_length = strlen(value);
Julien Boeuf84d964a2015-04-29 11:31:06 -0700306}
307
308void grpc_auth_property_reset(grpc_auth_property *property) {
Craig Tiller991edad2015-06-30 11:40:41 -0700309 gpr_free(property->name);
310 gpr_free(property->value);
Julien Boeuf84d964a2015-04-29 11:31:06 -0700311 memset(property, 0, sizeof(grpc_auth_property));
312}
313
Craig Tillerbd1795c2016-10-31 15:30:00 -0700314static void auth_context_pointer_arg_destroy(grpc_exec_ctx *exec_ctx, void *p) {
Julien Boeuf9a529082015-10-08 13:12:14 -0700315 GRPC_AUTH_CONTEXT_UNREF(p, "auth_context_pointer_arg");
316}
317
318static void *auth_context_pointer_arg_copy(void *p) {
319 return GRPC_AUTH_CONTEXT_REF(p, "auth_context_pointer_arg");
320}
321
Craig Tiller5de79ee2016-01-25 08:16:02 -0800322static int auth_context_pointer_cmp(void *a, void *b) { return GPR_ICMP(a, b); }
Craig Tilleredc2fff2016-01-13 06:54:27 -0800323
324static const grpc_arg_pointer_vtable auth_context_pointer_vtable = {
Craig Tiller5de79ee2016-01-25 08:16:02 -0800325 auth_context_pointer_arg_copy, auth_context_pointer_arg_destroy,
326 auth_context_pointer_cmp};
Craig Tilleredc2fff2016-01-13 06:54:27 -0800327
Julien Boeuf9a529082015-10-08 13:12:14 -0700328grpc_arg grpc_auth_context_to_arg(grpc_auth_context *p) {
Julien Boeuf66a27da2015-07-21 17:17:35 -0700329 grpc_arg arg;
330 memset(&arg, 0, sizeof(grpc_arg));
331 arg.type = GRPC_ARG_POINTER;
Julien Boeuf9a529082015-10-08 13:12:14 -0700332 arg.key = GRPC_AUTH_CONTEXT_ARG;
Julien Boeuf66a27da2015-07-21 17:17:35 -0700333 arg.value.pointer.p = p;
Craig Tilleredc2fff2016-01-13 06:54:27 -0800334 arg.value.pointer.vtable = &auth_context_pointer_vtable;
Julien Boeuf66a27da2015-07-21 17:17:35 -0700335 return arg;
336}
337
Craig Tiller0581d122015-11-02 14:09:40 -0800338grpc_auth_context *grpc_auth_context_from_arg(const grpc_arg *arg) {
Julien Boeuf9a529082015-10-08 13:12:14 -0700339 if (strcmp(arg->key, GRPC_AUTH_CONTEXT_ARG) != 0) return NULL;
Julien Boeuf66a27da2015-07-21 17:17:35 -0700340 if (arg->type != GRPC_ARG_POINTER) {
341 gpr_log(GPR_ERROR, "Invalid type %d for arg %s", arg->type,
Julien Boeuf9a529082015-10-08 13:12:14 -0700342 GRPC_AUTH_CONTEXT_ARG);
Julien Boeuf66a27da2015-07-21 17:17:35 -0700343 return NULL;
344 }
345 return arg->value.pointer.p;
346}
347
Julien Boeuf9a529082015-10-08 13:12:14 -0700348grpc_auth_context *grpc_find_auth_context_in_args(
Julien Boeuf66a27da2015-07-21 17:17:35 -0700349 const grpc_channel_args *args) {
350 size_t i;
351 if (args == NULL) return NULL;
352 for (i = 0; i < args->num_args; i++) {
Craig Tiller0581d122015-11-02 14:09:40 -0800353 grpc_auth_context *p = grpc_auth_context_from_arg(&args->args[i]);
Julien Boeuf66a27da2015-07-21 17:17:35 -0700354 if (p != NULL) return p;
355 }
356 return NULL;
357}