The FreeRADIUS server $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
Loading...
Searching...
No Matches
rlm_opendirectory.c
Go to the documentation of this file.
1/*
2 * This program is is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License, version 2 of the
4 * License as published by the Free Software Foundation.
5 *
6 * This program is distributed in the hope that it will be useful,
7 * but WITHOUT ANY WARRANTY; without even the implied warranty of
8 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 * GNU General Public License for more details.
10 *
11 * You should have received a copy of the GNU General Public License
12 * along with this program; if not, write to the Free Software
13 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
14 */
15
16/**
17 * $Id: 55c4c26babbcb88883144c0f55ad9caf33c682fc $
18 * @file rlm_opendirectory.c
19 * @brief Allows authentication against OpenDirectory and enforces ACLS.
20 *
21 * authentication: Apple Open Directory authentication
22 * authorization: enforces ACLs
23 *
24 * @copyright 2007 Apple Inc.
25 */
26
27/*
28 * For a typical Makefile, add linker flag like this:
29 * LDFLAGS = -framework DirectoryService
30 */
32#include <freeradius-devel/server/base.h>
33#include <freeradius-devel/server/module_rlm.h>
34#include <freeradius-devel/util/debug.h>
35#include <freeradius-devel/util/perm.h>
36
37#include <ctype.h>
38#include <stdlib.h>
39#include <string.h>
40#include <grp.h>
41#include <pwd.h>
42#include <sys/types.h>
43#include <sys/stat.h>
44
45#include <DirectoryService/DirectoryService.h>
46#include <membership.h>
47
51
52#ifndef HAVE_DECL_MBR_CHECK_SERVICE_MEMBERSHIP
53int mbr_check_service_membership(uuid_t const user, char const *servicename, int *ismember);
54#endif
55#ifndef HAVE_DECL_MBR_CHECK_MEMBERSHIP_REFRESH
56int mbr_check_membership_refresh(uuid_t const user, uuid_t group, int *ismember);
57#endif
58
59/* RADIUS service ACL constants */
60#define kRadiusSACLName "com.apple.access_radius"
61#define kRadiusServiceName "radius"
62
64static fr_dict_t const *dict_radius;
65
68 { .out = &dict_freeradius, .proto = "freeradius" },
69 { .out = &dict_radius, .proto = "radius" },
70 { NULL }
71};
72
76
79 { .out = &attr_auth_type, .name = "Auth-Type", .type = FR_TYPE_UINT32, .dict = &dict_freeradius },
80 { .out = &attr_user_name, .name = "User-Name", .type = FR_TYPE_STRING, .dict = &dict_radius },
81 { .out = &attr_user_password, .name = "User-Password", .type = FR_TYPE_STRING, .dict = &dict_radius },
82 { NULL }
83};
84
85/*
86 * od_check_passwd
87 *
88 * Returns: ds err
89 */
90
91static long od_check_passwd(request_t *request, char const *uname, char const *password)
92{
93 long result = eDSAuthFailed;
94 tDirReference dsRef = 0;
95 tDataBuffer *tDataBuff;
96 tDirNodeReference nodeRef = 0;
97 long status = eDSNoErr;
98 tContextData context = 0;
99 uint32_t nodeCount = 0;
100 uint32_t attrIndex = 0;
101 tDataList *nodeName = NULL;
102 tAttributeEntryPtr pAttrEntry = NULL;
103 tDataList *pRecName = NULL;
104 tDataList *pRecType = NULL;
105 tDataList *pAttrType = NULL;
106 uint32_t recCount = 0;
107 tRecordEntry *pRecEntry = NULL;
108 tAttributeListRef attrListRef = 0;
109 char *pUserLocation = NULL;
110 char *pUserName = NULL;
111 tAttributeValueListRef valueRef = 0;
112 tAttributeValueEntry *pValueEntry = NULL;
113 tDataList *pUserNode = NULL;
114 tDirNodeReference userNodeRef = 0;
115 tDataBuffer *pStepBuff = NULL;
116 tDataNode *pAuthType = NULL;
117 tAttributeValueEntry *pRecordType = NULL;
118 uint32_t uiCurr = 0;
119 uint32_t uiLen = 0;
120 uint32_t pwLen = 0;
121
122 if (!uname || !password)
123 return result;
124
125 do
126 {
127 status = dsOpenDirService( &dsRef );
128 if ( status != eDSNoErr )
129 return result;
130
131 tDataBuff = dsDataBufferAllocate( dsRef, 4096 );
132 if (!tDataBuff)
133 break;
134
135 /* find user on search node */
136 status = dsFindDirNodes( dsRef, tDataBuff, NULL, eDSSearchNodeName, &nodeCount, &context );
137 if (status != eDSNoErr || nodeCount < 1)
138 break;
139
140 status = dsGetDirNodeName( dsRef, tDataBuff, 1, &nodeName );
141 if (status != eDSNoErr)
142 break;
143
144 status = dsOpenDirNode( dsRef, nodeName, &nodeRef );
145 dsDataListDeallocate( dsRef, nodeName );
146 free( nodeName );
147 nodeName = NULL;
148 if (status != eDSNoErr)
149 break;
150
151 pRecName = dsBuildListFromStrings( dsRef, uname, NULL );
152 pRecType = dsBuildListFromStrings( dsRef, kDSStdRecordTypeUsers, kDSStdRecordTypeComputers, kDSStdRecordTypeMachines, NULL );
153 pAttrType = dsBuildListFromStrings( dsRef, kDSNAttrMetaNodeLocation, kDSNAttrRecordName, kDSNAttrRecordType, NULL );
154
155 recCount = 1;
156 status = dsGetRecordList( nodeRef, tDataBuff, pRecName, eDSExact, pRecType,
157 pAttrType, 0, &recCount, &context );
158 if ( status != eDSNoErr || recCount == 0 )
159 break;
160
161 status = dsGetRecordEntry( nodeRef, tDataBuff, 1, &attrListRef, &pRecEntry );
162 if ( status != eDSNoErr )
163 break;
164
165 for ( attrIndex = 1; (attrIndex <= pRecEntry->fRecordAttributeCount) && (status == eDSNoErr); attrIndex++ )
166 {
167 status = dsGetAttributeEntry( nodeRef, tDataBuff, attrListRef, attrIndex, &valueRef, &pAttrEntry );
168 if ( status == eDSNoErr && pAttrEntry != NULL )
169 {
170 if ( strcmp( pAttrEntry->fAttributeSignature.fBufferData, kDSNAttrMetaNodeLocation ) == 0 )
171 {
172 status = dsGetAttributeValue( nodeRef, tDataBuff, 1, valueRef, &pValueEntry );
173 if ( status == eDSNoErr && pValueEntry != NULL )
174 {
175 pUserLocation = talloc_zero_array(request, char, pValueEntry->fAttributeValueData.fBufferLength + 1);
176 memcpy( pUserLocation, pValueEntry->fAttributeValueData.fBufferData, pValueEntry->fAttributeValueData.fBufferLength );
177 }
178 }
179 else
180 if ( strcmp( pAttrEntry->fAttributeSignature.fBufferData, kDSNAttrRecordName ) == 0 )
181 {
182 status = dsGetAttributeValue( nodeRef, tDataBuff, 1, valueRef, &pValueEntry );
183 if ( status == eDSNoErr && pValueEntry != NULL )
184 {
185 pUserName = talloc_zero_array(request, char, pValueEntry->fAttributeValueData.fBufferLength + 1);
186 memcpy( pUserName, pValueEntry->fAttributeValueData.fBufferData, pValueEntry->fAttributeValueData.fBufferLength );
187 }
188 }
189 else
190 if ( strcmp( pAttrEntry->fAttributeSignature.fBufferData, kDSNAttrRecordType ) == 0 )
191 {
192 status = dsGetAttributeValue( nodeRef, tDataBuff, 1, valueRef, &pValueEntry );
193 if ( status == eDSNoErr && pValueEntry != NULL )
194 {
195 pRecordType = pValueEntry;
196 pValueEntry = NULL;
197 }
198 }
199
200 if ( pValueEntry != NULL ) {
201 dsDeallocAttributeValueEntry( dsRef, pValueEntry );
202 pValueEntry = NULL;
203 }
204 if ( pAttrEntry != NULL ) {
205 dsDeallocAttributeEntry( dsRef, pAttrEntry );
206 pAttrEntry = NULL;
207 }
208 dsCloseAttributeValueList( valueRef );
209 valueRef = 0;
210 }
211 }
212
213 pUserNode = dsBuildFromPath( dsRef, pUserLocation, "/" );
214 status = dsOpenDirNode( dsRef, pUserNode, &userNodeRef );
215 dsDataListDeallocate( dsRef, pUserNode );
216 free( pUserNode );
217 pUserNode = NULL;
218 if ( status != eDSNoErr )
219 break;
220
221 pStepBuff = dsDataBufferAllocate( dsRef, 128 );
222
223 pAuthType = dsDataNodeAllocateString( dsRef, kDSStdAuthNodeNativeClearTextOK );
224 uiCurr = 0;
225
226 if (!pUserName) {
227 RDEBUG2("Failed to find user name");
228 break;
229 }
230
231 /* User name */
232 uiLen = (uint32_t)strlen( pUserName );
233 memcpy( &(tDataBuff->fBufferData[ uiCurr ]), &uiLen, sizeof(uiLen) );
234 uiCurr += (uint32_t)sizeof( uiLen );
235 memcpy( &(tDataBuff->fBufferData[ uiCurr ]), pUserName, uiLen );
236 uiCurr += uiLen;
237
238 /* pw */
239 pwLen = (uint32_t)strlen( password );
240 memcpy( &(tDataBuff->fBufferData[ uiCurr ]), &pwLen, sizeof(pwLen) );
241 uiCurr += (uint32_t)sizeof( pwLen );
242 memcpy( &(tDataBuff->fBufferData[ uiCurr ]), password, pwLen );
243 uiCurr += pwLen;
244
245 tDataBuff->fBufferLength = uiCurr;
246
247 result = dsDoDirNodeAuthOnRecordType( userNodeRef, pAuthType, 1, tDataBuff, pStepBuff, NULL, &pRecordType->fAttributeValueData );
248 }
249 while ( 0 );
250
251 /* clean up */
252 if (pAuthType != NULL) {
253 dsDataNodeDeAllocate( dsRef, pAuthType );
254 pAuthType = NULL;
255 }
256 if (pRecordType != NULL) {
257 dsDeallocAttributeValueEntry( dsRef, pRecordType );
258 pRecordType = NULL;
259 }
260 if (tDataBuff != NULL) {
261 bzero( tDataBuff, tDataBuff->fBufferSize );
262 dsDataBufferDeAllocate( dsRef, tDataBuff );
263 tDataBuff = NULL;
264 }
265 if (pStepBuff != NULL) {
266 dsDataBufferDeAllocate( dsRef, pStepBuff );
267 pStepBuff = NULL;
268 }
269 if (pUserLocation != NULL) {
270 talloc_free(pUserLocation);
271 pUserLocation = NULL;
272 }
273 if (pUserName != NULL) {
274 talloc_free(pUserName);
275 pUserName = NULL;
276 }
277 if (pRecName != NULL) {
278 dsDataListDeallocate( dsRef, pRecName );
279 free( pRecName );
280 pRecName = NULL;
281 }
282 if (pRecType != NULL) {
283 dsDataListDeallocate( dsRef, pRecType );
284 free( pRecType );
285 pRecType = NULL;
286 }
287 if (pAttrType != NULL) {
288 dsDataListDeallocate( dsRef, pAttrType );
289 free( pAttrType );
290 pAttrType = NULL;
291 }
292 if (nodeRef != 0) {
293 dsCloseDirNode(nodeRef);
294 nodeRef = 0;
295 }
296 if (dsRef != 0) {
297 dsCloseDirService(dsRef);
298 dsRef = 0;
299 }
300
301 return result;
302}
303
304
305/*
306 * Check the users password against the standard UNIX
307 * password table.
308 */
309static unlang_action_t CC_HINT(nonnull) mod_authenticate(rlm_rcode_t *p_result, UNUSED module_ctx_t const *mctx, request_t *request)
310{
311 int ret;
312 long odResult = eDSAuthFailed;
313 fr_pair_t *username, *password;
314
315 username = fr_pair_find_by_da(&request->request_pairs, NULL, attr_user_name);
316 password = fr_pair_find_by_da(&request->request_pairs, NULL, attr_user_password);
317
318 /*
319 * We can only authenticate user requests which HAVE
320 * a User-Name attribute.
321 */
322 if (!username) {
323 REDEBUG("Attribute \"User-Name\" is required for authentication");
325 }
326
327 if (!password) {
328 REDEBUG("Attribute \"User-Password\" is required for authentication");
330 }
331
332 /*
333 * Make sure the supplied password isn't empty
334 */
335 if (password->vp_length == 0) {
336 REDEBUG("User-Password must not be empty");
338 }
339
340 /*
341 * Log the password
342 */
343 if (RDEBUG_ENABLED3) {
344 RDEBUG("Login attempt with password \"%pV\"", &password->data);
345 } else {
346 RDEBUG2("Login attempt with password");
347 }
348
349 odResult = od_check_passwd(request, username->vp_strvalue,
350 password->vp_strvalue);
351 switch (odResult) {
352 case eDSNoErr:
353 ret = RLM_MODULE_OK;
354 break;
355
356 case eDSAuthUnknownUser:
357 case eDSAuthInvalidUserName:
358 case eDSAuthNewPasswordRequired:
359 case eDSAuthPasswordExpired:
360 case eDSAuthAccountDisabled:
361 case eDSAuthAccountExpired:
362 case eDSAuthAccountInactive:
363 case eDSAuthInvalidLogonHours:
364 case eDSAuthInvalidComputer:
366 break;
367
368 default:
369 ret = RLM_MODULE_REJECT;
370 break;
371 }
372
373 if (ret != RLM_MODULE_OK) {
374 RDEBUG2("Invalid password: %pV", &username->data);
375 return ret;
376 }
377
379}
380
381
382/*
383 * member of the radius group?
384 */
385static unlang_action_t CC_HINT(nonnull) mod_authorize(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
386{
388 struct passwd *userdata = NULL;
389 int ismember = 0;
390 fr_client_t *client = NULL;
391 uuid_t uuid;
392 uuid_t guid_sacl;
393 uuid_t guid_nasgroup;
394 int err;
395 char host_ipaddr[128] = {0};
396 gid_t gid;
398
399 /*
400 * We can only authenticate user requests which HAVE
401 * a User-Name attribute.
402 */
403 username = fr_pair_find_by_da(&request->request_pairs, NULL, attr_user_name);
404 if (!username) {
405 RDEBUG2("OpenDirectory requires a User-Name attribute");
407 }
408
409 /* resolve SACL */
410 uuid_clear(guid_sacl);
411
412 if (fr_perm_gid_from_str(request, &gid, kRadiusSACLName) < 0) {
413 RDEBUG2("The SACL group \"%s\" does not exist on this system", kRadiusSACLName);
414 } else {
415 err = mbr_gid_to_uuid(gid, guid_sacl);
416 if (err != 0) {
417 REDEBUG("The group \"%s\" does not have a GUID", kRadiusSACLName);
419 }
420 }
421
422 /* resolve client access list */
423 uuid_clear(guid_nasgroup);
424
425 client = client_from_request(request);
426#if 0
427 if (client->community[0] != '\0' ) {
428 /*
429 * The "community" can be a GUID (Globally Unique ID) or
430 * a group name
431 */
432 if (uuid_parse(client->community, guid_nasgroup) != 0) {
433 /* attempt to resolve the name */
434 groupdata = getgrnam(client->community);
435 if (!groupdata) {
436 REDEBUG("The group \"%s\" does not exist on this system", client->community);
438 }
439 err = mbr_gid_to_uuid(groupdata->gr_gid, guid_nasgroup);
440 if (err != 0) {
441 REDEBUG("The group \"%s\" does not have a GUID", client->community);
443 }
444 }
445 }
446 else
447#endif
448 {
449 if (!client) {
450 RDEBUG2("The client record could not be found for host %s",
451 fr_inet_ntoh(&request->packet->socket.inet.src_ipaddr, host_ipaddr, sizeof(host_ipaddr)));
452 } else {
453 RDEBUG2("The host %s does not have an access group",
454 fr_inet_ntoh(&request->packet->socket.inet.src_ipaddr, host_ipaddr, sizeof(host_ipaddr)));
455 }
456 }
457
458 if (uuid_is_null(guid_sacl) && uuid_is_null(guid_nasgroup)) {
459 RDEBUG2("No access control groups, all users allowed");
460 goto setup_auth_type;
461 }
462
463 /* resolve user */
464 uuid_clear(uuid);
465
466 fr_perm_getpwnam(request, &userdata, username->vp_strvalue);
467 if (userdata != NULL) {
468 err = mbr_uid_to_uuid(userdata->pw_uid, uuid);
469 if (err != 0)
470 uuid_clear(uuid);
471 }
472 talloc_free(userdata);
473
474 if (uuid_is_null(uuid)) {
475 REDEBUG("Could not get the user's uuid");
477 }
478
479 if (!uuid_is_null(guid_sacl)) {
481 if (err != 0) {
482 REDEBUG("Failed to check group membership");
484 }
485
486 if (ismember == 0) {
487 REDEBUG("User is not authorized");
489 }
490 }
491
492 if (!uuid_is_null(guid_nasgroup)) {
493 err = mbr_check_membership_refresh(uuid, guid_nasgroup, &ismember);
494 if (err != 0) {
495 REDEBUG("Failed to check group membership");
497 }
498
499 if (ismember == 0) {
500 REDEBUG("User is not authorized");
502 }
503 }
504
505setup_auth_type:
506 if (!inst->auth_type) {
507 WARN("No 'authenticate %s {...}' section or 'Auth-Type = %s' set. Cannot setup OpenDirectory authentication",
508 mctx->mi->name, mctx->mi->name);
510 }
511
513
515}
516
517static int mod_instantiate(module_inst_ctx_t const *mctx)
518{
519 rlm_opendirectory_t *inst = talloc_get_type_abort(mctx->mi->data, rlm_opendirectory_t);
520
521 inst->auth_type = fr_dict_enum_by_name(attr_auth_type, mctx->mi->name, -1);
522 if (!inst->auth_type) {
523 WARN("Failed to find 'authenticate %s {...}' section. OpenDirectory authentication will likely not work",
524 mctx->mi->name);
525 }
526
527 return 0;
528}
529
530/* globally exported name */
533 .common = {
534 .magic = MODULE_MAGIC_INIT,
535 .name = "opendirectory",
538 },
539 .method_group = {
540 .bindings = (module_method_binding_t[]){
541 { .section = SECTION_NAME("authenticate", CF_IDENT_ANY), .method = mod_authenticate },
542 { .section = SECTION_NAME("recv", CF_IDENT_ANY), .method = mod_authorize },
544 }
545 }
546};
unlang_action_t
Returned by unlang_op_t calls, determine the next action of the interpreter.
Definition action.h:35
static int context
Definition radmin.c:71
#define USES_APPLE_DEPRECATED_API
Definition build.h:470
#define UNUSED
Definition build.h:315
#define CF_IDENT_ANY
Definition cf_util.h:78
static fr_slen_t err
Definition dict.h:824
fr_dict_attr_t const ** out
Where to write a pointer to the resolved fr_dict_attr_t.
Definition dict.h:268
fr_dict_t const ** out
Where to write a pointer to the loaded/resolved fr_dict_t.
Definition dict.h:281
fr_dict_enum_value_t * fr_dict_enum_by_name(fr_dict_attr_t const *da, char const *name, ssize_t len)
Definition dict_util.c:3395
Specifies an attribute which must be present for the module to function.
Definition dict.h:267
Specifies a dictionary which must be loaded/loadable for the module to function.
Definition dict.h:280
Value of an enumerated attribute.
Definition dict.h:227
#define MODULE_MAGIC_INIT
Stop people using different module/library/server versions together.
Definition dl_module.h:63
free(array)
char const * fr_inet_ntoh(fr_ipaddr_t const *src, char *out, size_t outlen)
Perform reverse resolution of an IP address.
Definition inet.c:355
Describes a host allowed to send packets to the server.
Definition client.h:80
#define RDEBUG_ENABLED3
True if request debug level 1-3 messages are enabled.
Definition log.h:335
talloc_free(reap)
@ FR_TYPE_STRING
String of printable characters.
@ FR_TYPE_UINT32
32 Bit unsigned integer.
unsigned int uint32_t
module_instance_t const * mi
Instance of the module being instantiated.
Definition module_ctx.h:42
module_instance_t * mi
Instance of the module being instantiated.
Definition module_ctx.h:51
Temporary structure to hold arguments for module calls.
Definition module_ctx.h:41
Temporary structure to hold arguments for instantiation calls.
Definition module_ctx.h:50
bool module_rlm_section_type_set(request_t *request, fr_dict_attr_t const *type_da, fr_dict_enum_value_t const *enumv)
Set the next section type if it's not already set.
Definition module_rlm.c:427
module_t common
Common fields presented by all modules.
Definition module_rlm.h:39
fr_pair_t * fr_pair_find_by_da(fr_pair_list_t const *list, fr_pair_t const *prev, fr_dict_attr_t const *da)
Find the first pair with a matching da.
Definition pair.c:693
int fr_perm_getpwnam(TALLOC_CTX *ctx, struct passwd **out, char const *name)
Resolve a username to a passwd entry.
Definition perm.c:266
int fr_perm_gid_from_str(TALLOC_CTX *ctx, gid_t *out, char const *name)
Resolve a group name to a GID.
Definition perm.c:473
#define REDEBUG(fmt,...)
Definition radclient.h:52
#define RDEBUG2(fmt,...)
Definition radclient.h:54
#define RDEBUG(fmt,...)
Definition radclient.h:53
#define WARN(fmt,...)
Definition radclient.h:47
#define RETURN_MODULE_NOOP
Definition rcode.h:62
#define RETURN_MODULE_INVALID
Definition rcode.h:59
#define RETURN_MODULE_OK
Definition rcode.h:57
#define RETURN_MODULE_FAIL
Definition rcode.h:56
#define RETURN_MODULE_DISALLOW
Definition rcode.h:60
rlm_rcode_t
Return codes indicating the result of the module call.
Definition rcode.h:40
@ RLM_MODULE_OK
The module is OK, continue.
Definition rcode.h:43
@ RLM_MODULE_DISALLOW
Reject the request (user is locked out).
Definition rcode.h:46
@ RLM_MODULE_REJECT
Immediately reject the request.
Definition rcode.h:41
#define RETURN_MODULE_NOTFOUND
Definition rcode.h:61
static fr_dict_attr_t const * attr_user_password
static fr_dict_t const * dict_freeradius
fr_dict_enum_value_t * auth_type
static unlang_action_t mod_authenticate(rlm_rcode_t *p_result, UNUSED module_ctx_t const *mctx, request_t *request)
#define kRadiusSACLName
static fr_dict_t const * dict_radius
#define kRadiusServiceName
static fr_dict_attr_t const * attr_auth_type
int mbr_check_membership_refresh(uuid_t const user, uuid_t group, int *ismember)
static unlang_action_t mod_authorize(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
fr_dict_attr_autoload_t rlm_opendirectory_dict_attr[]
int mbr_check_service_membership(uuid_t const user, char const *servicename, int *ismember)
static fr_dict_attr_t const * attr_user_name
static long od_check_passwd(request_t *request, char const *uname, char const *password)
module_rlm_t rlm_opendirectory
fr_dict_autoload_t rlm_opendirectory_dict[]
static int mod_instantiate(module_inst_ctx_t const *mctx)
static int instantiate(module_inst_ctx_t const *mctx)
Definition rlm_rest.c:1310
username
#define SECTION_NAME(_name1, _name2)
Define a section name consisting of a verb and a noun.
Definition section.h:40
char const * name
Instance name e.g. user_database.
Definition module.h:335
size_t inst_size
Size of the module's instance data.
Definition module.h:203
void * data
Module's instance data.
Definition module.h:271
#define MODULE_BINDING_TERMINATOR
Terminate a module binding list.
Definition module.h:151
Named methods exported by a module.
Definition module.h:173
fr_client_t * client_from_request(request_t *request)
Search up a list of requests trying to locate one which has a client.
Definition client.c:1112
eap_aka_sim_process_conf_t * inst
Stores an attribute, a value and various bits of other data.
Definition pair.h:68
#define talloc_get_type_abort_const
Definition talloc.h:282
int nonnull(2, 5))