rkerberos 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -9,6 +9,7 @@ VALUE cKadm5PrincipalNotFoundException;
9
9
  // Prototype
10
10
  static VALUE rkadm5_close(VALUE);
11
11
  static void free_tl_data(krb5_tl_data *);
12
+ static void free_db_args(char**);
12
13
  char** parse_db_args(VALUE v_db_args);
13
14
  void add_db_args(kadm5_principal_ent_rec*, char**);
14
15
  void add_tl_data(krb5_int16 *, krb5_tl_data **,
@@ -16,6 +17,13 @@ void add_tl_data(krb5_int16 *, krb5_tl_data **,
16
17
 
17
18
 
18
19
  // TypedData functions for RUBY_KADM5
20
+ static void rkadm5_typed_mark(void *ptr) {
21
+ if (!ptr) return;
22
+ RUBY_KADM5 *k = (RUBY_KADM5 *)ptr;
23
+ if (k->rb_context != Qnil)
24
+ rb_gc_mark(k->rb_context);
25
+ }
26
+
19
27
  static void rkadm5_typed_free(void *ptr) {
20
28
  if (!ptr) return;
21
29
  RUBY_KADM5 *k = (RUBY_KADM5 *)ptr;
@@ -23,9 +31,9 @@ static void rkadm5_typed_free(void *ptr) {
23
31
  kadm5_destroy(k->handle);
24
32
  if (k->princ)
25
33
  krb5_free_principal(k->ctx, k->princ);
26
- if (k->ctx)
34
+ if (k->ctx && k->rb_context == Qnil)
27
35
  krb5_free_context(k->ctx);
28
- free(k->db_args);
36
+ free_db_args(k->db_args);
29
37
  free(k);
30
38
  }
31
39
 
@@ -35,7 +43,7 @@ static size_t rkadm5_typed_size(const void *ptr) {
35
43
 
36
44
  static const rb_data_type_t rkadm5_data_type = {
37
45
  "RUBY_KADM5",
38
- {NULL, rkadm5_typed_free, rkadm5_typed_size,},
46
+ {rkadm5_typed_mark, rkadm5_typed_free, rkadm5_typed_size,},
39
47
  NULL, NULL, RUBY_TYPED_FREE_IMMEDIATELY
40
48
  };
41
49
 
@@ -43,34 +51,48 @@ static const rb_data_type_t rkadm5_data_type = {
43
51
  static VALUE rkadm5_allocate(VALUE klass){
44
52
  RUBY_KADM5* ptr = ALLOC(RUBY_KADM5);
45
53
  memset(ptr, 0, sizeof(RUBY_KADM5));
54
+ ptr->rb_context = Qnil;
46
55
  return TypedData_Wrap_Struct(klass, &rkadm5_data_type, ptr);
47
56
  }
48
57
 
49
58
  /*
50
59
  * call-seq:
51
- * Kerberos::Kadm5.new(:principal => 'name', :password => 'xxxxx')
52
- * Kerberos::Kadm5.new(:principal => 'name', :keytab => '/path/to/your/keytab')
53
- * Kerberos::Kadm5.new(:principal => 'name', :keytab => true)
60
+ * Kerberos::Kadm5.new(principal: 'name', password: 'xxxxx')
61
+ * Kerberos::Kadm5.new(principal: 'name', keytab: '/path/to/your/keytab')
62
+ * Kerberos::Kadm5.new(principal: 'name', keytab: true)
63
+ * Kerberos::Kadm5.new(principal: 'name', ccache: ccache_object)
54
64
  *
55
- * Creates and returns a new Kerberos::Kadm5 object. A hash argument is
56
- * accepted that allows you to specify a principal and a password, or
57
- * a keytab file.
65
+ * Creates and returns a new Kerberos::Kadm5 object. Keyword arguments allow
66
+ * you to specify a principal and a password, a keytab file, or a credentials
67
+ * cache.
58
68
  *
59
69
  * If you pass a string as the :keytab value it will attempt to use that file
60
70
  * for the keytab. If you pass true as the value it will attempt to use the
61
71
  * default keytab file, typically /etc/krb5.keytab.
62
72
  *
73
+ * If you pass a Kerberos::Krb5::CredentialsCache object as the :ccache value,
74
+ * it will authenticate using the credentials stored in that cache via
75
+ * kadm5_init_with_creds.
76
+ *
63
77
  * You may also pass the :service option to specify the service name. The
64
78
  * default is kadmin/admin.
65
79
  *
66
80
  * There is also a :db_args option, which is a single string or array of strings
67
81
  * containing options usually passed to kadmin with the -x switch. For a list of
68
- * available options, see the kadmin manpage
82
+ * available options, see the kadmin manpage.
83
+ *
84
+ * Only one of :password, :keytab, or :ccache may be specified.
69
85
  *
70
86
  */
71
- static VALUE rkadm5_initialize(VALUE self, VALUE v_opts){
87
+ static VALUE rkadm5_initialize(int argc, VALUE* argv, VALUE self){
72
88
  RUBY_KADM5* ptr;
73
- VALUE v_principal, v_password, v_keytab, v_service, v_db_args;
89
+ VALUE v_opts, v_principal, v_password, v_keytab, v_service, v_db_args, v_context, v_ccache;
90
+ ID kw_table[7] = {
91
+ rb_intern("principal"), rb_intern("password"), rb_intern("keytab"),
92
+ rb_intern("ccache"), rb_intern("service"), rb_intern("db_args"),
93
+ rb_intern("context")
94
+ };
95
+ VALUE kw_vals[7];
74
96
  char* user;
75
97
  char* pass = NULL;
76
98
  char* keytab = NULL;
@@ -79,35 +101,59 @@ static VALUE rkadm5_initialize(VALUE self, VALUE v_opts){
79
101
  krb5_error_code kerror;
80
102
 
81
103
  TypedData_Get_Struct(self, RUBY_KADM5, &rkadm5_data_type, ptr);
82
- Check_Type(v_opts, T_HASH);
83
-
84
- // Accept both string and symbol keys
85
- v_principal = rb_hash_aref2(v_opts, rb_str_new_cstr("principal"));
86
- if (NIL_P(v_principal)) v_principal = rb_hash_aref2(v_opts, ID2SYM(rb_intern("principal")));
87
-
88
- // Principal must be specified
89
- if(NIL_P(v_principal))
90
- rb_raise(rb_eArgError, "principal must be specified");
91
-
92
- Check_Type(v_principal, T_STRING);
93
- user = StringValueCStr(v_principal);
94
-
95
- v_password = rb_hash_aref2(v_opts, rb_str_new_cstr("password"));
96
- if (NIL_P(v_password)) v_password = rb_hash_aref2(v_opts, ID2SYM(rb_intern("password")));
97
- v_keytab = rb_hash_aref2(v_opts, rb_str_new_cstr("keytab"));
98
- if (NIL_P(v_keytab)) v_keytab = rb_hash_aref2(v_opts, ID2SYM(rb_intern("keytab")));
99
-
100
- if(RTEST(v_password) && RTEST(v_keytab))
101
- rb_raise(rb_eArgError, "cannot use both a password and a keytab");
104
+ rb_scan_args(argc, argv, "0:", &v_opts);
105
+
106
+ if(NIL_P(v_opts))
107
+ v_opts = rb_hash_new();
108
+
109
+ rb_get_kwargs(v_opts, kw_table, 0, 7, kw_vals);
110
+ v_principal = kw_vals[0] == Qundef ? Qnil : kw_vals[0];
111
+ v_password = kw_vals[1] == Qundef ? Qnil : kw_vals[1];
112
+ v_keytab = kw_vals[2] == Qundef ? Qnil : kw_vals[2];
113
+ v_ccache = kw_vals[3] == Qundef ? Qnil : kw_vals[3];
114
+ v_service = kw_vals[4] == Qundef ? Qnil : kw_vals[4];
115
+ v_db_args = kw_vals[5] == Qundef ? Qnil : kw_vals[5];
116
+ v_context = kw_vals[6] == Qundef ? Qnil : kw_vals[6];
117
+
118
+ // Validate mutual exclusivity
119
+ {
120
+ int auth_count = 0;
121
+ if(RTEST(v_password)) auth_count++;
122
+ if(RTEST(v_keytab)) auth_count++;
123
+ if(RTEST(v_ccache)) auth_count++;
124
+
125
+ if(auth_count > 1)
126
+ rb_raise(rb_eArgError, "only one of password, keytab, or ccache may be specified");
127
+
128
+ if(auth_count == 0)
129
+ rb_raise(rb_eArgError, "one of password, keytab, or ccache must be specified");
130
+ }
102
131
 
132
+ // Principal must be specified if using a password
103
133
  if(RTEST(v_password)){
134
+ if(NIL_P(v_principal))
135
+ rb_raise(rb_eArgError, "principal must be specified");
136
+
104
137
  Check_Type(v_password, T_STRING);
138
+ Check_Type(v_principal, T_STRING);
139
+
105
140
  pass = StringValueCStr(v_password);
106
141
  }
107
142
 
108
- v_service = rb_hash_aref2(v_opts, rb_str_new_cstr("service"));
109
- if (NIL_P(v_service)) v_service = rb_hash_aref2(v_opts, ID2SYM(rb_intern("service")));
143
+ if(RTEST(v_ccache) && NIL_P(v_principal)){
144
+ if(NIL_P(v_principal))
145
+ v_principal = rb_funcall(v_ccache, rb_intern("principal"), 0);
146
+ }
110
147
 
148
+ // For a keytab use the first entry's principal
149
+ if(RTEST(v_keytab) && NIL_P(v_principal)) {
150
+ VALUE v_enum, v_first;
151
+ v_enum = rb_funcall(v_keytab, rb_intern("each"), 0);
152
+ v_first = rb_funcall(v_enum, rb_intern("first"), 0);
153
+ v_principal = rb_iv_get(v_first, "@principal");
154
+ }
155
+
156
+ user = StringValueCStr(v_principal);
111
157
  if(NIL_P(v_service)){
112
158
  service = (char *) "kadmin/admin";
113
159
  }
@@ -116,16 +162,31 @@ static VALUE rkadm5_initialize(VALUE self, VALUE v_opts){
116
162
  service = StringValueCStr(v_service);
117
163
  }
118
164
 
119
- v_db_args = rb_hash_aref2(v_opts, rb_str_new_cstr("db_args"));
120
- if (NIL_P(v_db_args)) v_db_args = rb_hash_aref2(v_opts, ID2SYM(rb_intern("db_args")));
121
165
  ptr->db_args = parse_db_args(v_db_args);
122
166
 
123
- // Normally I would wait to initialize the context, but we might need it
124
- // to get the default keytab file name.
125
- kerror = krb5_init_context(&ptr->ctx);
167
+ // Initialize or borrow the context
168
+ if(!NIL_P(v_context)){
169
+ RUBY_KRB5_CONTEXT* ctx_ptr;
126
170
 
127
- if(kerror)
128
- rb_raise(cKadm5Exception, "krb5_init_context: %s", error_message(kerror));
171
+ if(!rb_obj_is_kind_of(v_context, cKrb5Context))
172
+ rb_raise(rb_eTypeError, "context must be a Kerberos::Krb5::Context object");
173
+
174
+ TypedData_Get_Struct(v_context, RUBY_KRB5_CONTEXT, &rkrb5_context_data_type, ctx_ptr);
175
+
176
+ if(!ctx_ptr->ctx)
177
+ rb_raise(cKrb5Exception, "context is closed");
178
+
179
+ ptr->ctx = ctx_ptr->ctx;
180
+ ptr->rb_context = v_context;
181
+ }
182
+ else{
183
+ kerror = krb5_init_context(&ptr->ctx);
184
+
185
+ if(kerror)
186
+ rb_raise(cKadm5Exception, "krb5_init_context: %s", error_message(kerror));
187
+
188
+ ptr->rb_context = Qnil;
189
+ }
129
190
 
130
191
  // The docs say I can use NULL to get the default, but reality appears to be otherwise.
131
192
  if(RTEST(v_keytab)){
@@ -175,8 +236,31 @@ static VALUE rkadm5_initialize(VALUE self, VALUE v_opts){
175
236
  if(kerror)
176
237
  rb_raise(cKadm5Exception, "kadm5_init_with_skey: %s", error_message(kerror));
177
238
  }
178
- else{
179
- // TODO: Credentials cache.
239
+ else if(RTEST(v_ccache)){
240
+ RUBY_KRB5_CCACHE* cc_ptr;
241
+
242
+ if(!rb_obj_is_kind_of(v_ccache, cKrb5CCache))
243
+ rb_raise(rb_eTypeError, "ccache must be a Kerberos::Krb5::CredentialsCache object");
244
+
245
+ TypedData_Get_Struct(v_ccache, RUBY_KRB5_CCACHE, &rkrb5_ccache_data_type, cc_ptr);
246
+
247
+ if(!cc_ptr->ccache)
248
+ rb_raise(cKrb5Exception, "credentials cache is closed or destroyed");
249
+
250
+ kerror = kadm5_init_with_creds(
251
+ ptr->ctx,
252
+ user,
253
+ cc_ptr->ccache,
254
+ service,
255
+ NULL,
256
+ KADM5_STRUCT_VERSION,
257
+ KADM5_API_VERSION_3,
258
+ ptr->db_args,
259
+ &ptr->handle
260
+ );
261
+
262
+ if(kerror)
263
+ rb_raise(cKadm5Exception, "kadm5_init_with_creds: %s", error_message(kerror));
180
264
  }
181
265
 
182
266
  if(rb_block_given_p()){
@@ -280,50 +364,148 @@ static VALUE rkadm5_set_pwexpire(VALUE self, VALUE v_user, VALUE v_pwexpire){
280
364
 
281
365
  /*
282
366
  * call-seq:
283
- * kadm5.create_principal(name, password, db_args=nil)
284
- * kadm5.create_principal(principal)
367
+ * kadm5.create_principal(name:, password:, db_args: nil)
368
+ * kadm5.create_principal(principal:, password:, db_args: nil)
369
+ *
370
+ * Creates a new principal with an initial password of +password+.
285
371
  *
286
- * Creates a new principal +name+ with an initial password of +password+.
287
- * +db_args+ is an optional string or array of strings containing options that are usually
288
- * passed to add_principal with the -x option. For a list of options, see the kadmin manpage,
289
- * in the add_principal section.
290
- *--
291
- * TODO: Allow a Principal object to be passed in as an argument.
372
+ * The principal may be specified either as a +name+ string or as a
373
+ * +principal+ object (a Kerberos::Krb5::Principal). When a Principal
374
+ * object is provided, any non-nil writable attributes on that object
375
+ * are forwarded to the KDC:
376
+ *
377
+ * * +policy+
378
+ * * +expire_time+
379
+ * * +password_expiration+
380
+ * * +max_life+
381
+ * * +max_renewable_life+
382
+ * * +attributes+
383
+ *
384
+ * +db_args+ is an optional string or array of strings containing options
385
+ * that are usually passed to add_principal with the -x option. For a
386
+ * list of options, see the kadmin manpage, in the add_principal section.
292
387
  */
293
388
  static VALUE rkadm5_create_principal(int argc, VALUE* argv, VALUE self){
294
389
  RUBY_KADM5* ptr;
295
- char* user;
296
390
  char* pass;
297
391
  char** db_args;
298
392
  int mask;
299
393
  kadm5_principal_ent_rec princ;
300
394
  krb5_error_code kerror;
301
- VALUE v_user, v_pass, v_db_args;
395
+ VALUE v_opts, v_name, v_principal, v_pass, v_db_args;
396
+ ID kw_table[4] = { rb_intern("name"), rb_intern("principal"), rb_intern("password"), rb_intern("db_args") };
397
+ VALUE kw_vals[4];
302
398
 
303
399
  TypedData_Get_Struct(self, RUBY_KADM5, &rkadm5_data_type, ptr);
304
400
 
305
- rb_scan_args(argc, argv, "21", &v_user, &v_pass, &v_db_args);
306
- Check_Type(v_user, T_STRING);
401
+ rb_scan_args(argc, argv, "0:", &v_opts);
402
+
403
+ if(NIL_P(v_opts))
404
+ v_opts = rb_hash_new();
405
+
406
+ rb_get_kwargs(v_opts, kw_table, 0, 4, kw_vals);
407
+ v_name = kw_vals[0] == Qundef ? Qnil : kw_vals[0];
408
+ v_principal = kw_vals[1] == Qundef ? Qnil : kw_vals[1];
409
+ v_pass = kw_vals[2] == Qundef ? Qnil : kw_vals[2];
410
+ v_db_args = kw_vals[3] == Qundef ? Qnil : kw_vals[3];
411
+
412
+ if(NIL_P(v_pass))
413
+ rb_raise(rb_eArgError, "password: is required");
414
+
307
415
  Check_Type(v_pass, T_STRING);
308
416
 
417
+ if(NIL_P(v_name) && NIL_P(v_principal))
418
+ rb_raise(rb_eArgError, "name: or principal: is required");
419
+
420
+ if(RTEST(v_name) && RTEST(v_principal))
421
+ rb_raise(rb_eArgError, "name: and principal: are mutually exclusive");
422
+
309
423
  memset(&princ, 0, sizeof(princ));
310
424
 
311
425
  mask = KADM5_PRINCIPAL | KADM5_TL_DATA;
312
- user = StringValueCStr(v_user);
313
426
  pass = StringValueCStr(v_pass);
314
427
 
315
428
  db_args = parse_db_args(v_db_args);
316
429
  add_db_args(&princ, db_args);
317
- free(db_args);
430
+ free_db_args(db_args);
318
431
 
319
432
  if(!ptr->ctx)
320
433
  rb_raise(cKadm5Exception, "no context has been established");
321
434
 
322
- kerror = krb5_parse_name(ptr->ctx, user, &princ.principal);
435
+ // Determine the principal name and populate mask from the principal object
436
+ if(RTEST(v_principal)){
437
+ VALUE v_princ_name, v_policy, v_expire, v_pw_expire, v_max_life, v_max_renew, v_attrs;
323
438
 
324
- if(kerror){
325
- free_tl_data(princ.tl_data);
326
- rb_raise(cKadm5Exception, "krb5_parse_name: %s", error_message(kerror));
439
+ if(!rb_obj_is_kind_of(v_principal, cKrb5Principal))
440
+ rb_raise(rb_eTypeError, "principal: must be a Kerberos::Krb5::Principal object");
441
+
442
+ v_princ_name = rb_iv_get(v_principal, "@principal");
443
+
444
+ if(NIL_P(v_princ_name))
445
+ rb_raise(rb_eArgError, "principal object has no name set");
446
+
447
+ Check_Type(v_princ_name, T_STRING);
448
+
449
+ kerror = krb5_parse_name(ptr->ctx, StringValueCStr(v_princ_name), &princ.principal);
450
+
451
+ if(kerror){
452
+ free_tl_data(princ.tl_data);
453
+ rb_raise(cKadm5Exception, "krb5_parse_name: %s", error_message(kerror));
454
+ }
455
+
456
+ // Forward optional attributes from the Principal object
457
+ v_policy = rb_iv_get(v_principal, "@policy");
458
+
459
+ if(RTEST(v_policy)){
460
+ Check_Type(v_policy, T_STRING);
461
+ princ.policy = StringValueCStr(v_policy);
462
+ mask |= KADM5_POLICY;
463
+ }
464
+
465
+ v_expire = rb_iv_get(v_principal, "@expire_time");
466
+
467
+ if(RTEST(v_expire)){
468
+ princ.princ_expire_time = (krb5_timestamp)NUM2LONG(rb_funcall(v_expire, rb_intern("to_i"), 0));
469
+ mask |= KADM5_PRINC_EXPIRE_TIME;
470
+ }
471
+
472
+ v_pw_expire = rb_iv_get(v_principal, "@password_expiration");
473
+
474
+ if(RTEST(v_pw_expire)){
475
+ princ.pw_expiration = (krb5_timestamp)NUM2LONG(rb_funcall(v_pw_expire, rb_intern("to_i"), 0));
476
+ mask |= KADM5_PW_EXPIRATION;
477
+ }
478
+
479
+ v_max_life = rb_iv_get(v_principal, "@max_life");
480
+
481
+ if(RTEST(v_max_life)){
482
+ princ.max_life = NUM2LONG(v_max_life);
483
+ mask |= KADM5_MAX_LIFE;
484
+ }
485
+
486
+ v_max_renew = rb_iv_get(v_principal, "@max_renewable_life");
487
+
488
+ if(RTEST(v_max_renew)){
489
+ princ.max_renewable_life = NUM2LONG(v_max_renew);
490
+ mask |= KADM5_MAX_RLIFE;
491
+ }
492
+
493
+ v_attrs = rb_iv_get(v_principal, "@attributes");
494
+
495
+ if(RTEST(v_attrs)){
496
+ princ.attributes = NUM2LONG(v_attrs);
497
+ mask |= KADM5_ATTRIBUTES;
498
+ }
499
+ }
500
+ else{
501
+ Check_Type(v_name, T_STRING);
502
+
503
+ kerror = krb5_parse_name(ptr->ctx, StringValueCStr(v_name), &princ.principal);
504
+
505
+ if(kerror){
506
+ free_tl_data(princ.tl_data);
507
+ rb_raise(cKadm5Exception, "krb5_parse_name: %s", error_message(kerror));
508
+ }
327
509
  }
328
510
 
329
511
  kerror = kadm5_create_principal(ptr->handle, &princ, mask, pass);
@@ -395,15 +577,16 @@ static VALUE rkadm5_close(VALUE self){
395
577
  if(ptr->princ)
396
578
  krb5_free_principal(ptr->ctx, ptr->princ);
397
579
 
398
- if(ptr->ctx)
580
+ if(ptr->ctx && ptr->rb_context == Qnil)
399
581
  krb5_free_context(ptr->ctx);
400
582
 
401
- free(ptr->db_args);
583
+ free_db_args(ptr->db_args);
402
584
 
403
585
  ptr->db_args = NULL;
404
586
  ptr->ctx = NULL;
405
587
  ptr->princ = NULL;
406
588
  ptr->handle = NULL;
589
+ ptr->rb_context = Qnil;
407
590
 
408
591
  return self;
409
592
  }
@@ -412,11 +595,11 @@ static VALUE rkadm5_close(VALUE self){
412
595
  static VALUE create_principal_from_entry(VALUE v_name, RUBY_KADM5* ptr, kadm5_principal_ent_rec* ent){
413
596
  krb5_error_code kerror;
414
597
  VALUE v_principal;
415
- VALUE v_args[1];
598
+ VALUE v_opts = rb_hash_new();
416
599
 
417
- v_args[0] = v_name;
600
+ rb_hash_aset(v_opts, ID2SYM(rb_intern("name")), v_name);
418
601
 
419
- v_principal = rb_class_new_instance(1, v_args, cKrb5Principal);
602
+ v_principal = rb_class_new_instance_kw(1, &v_opts, cKrb5Principal, RB_PASS_KEYWORDS);
420
603
 
421
604
  rb_iv_set(v_principal, "@attributes", LONG2FIX(ent->attributes));
422
605
  rb_iv_set(v_principal, "@aux_attributes", INT2FIX(ent->aux_attributes));
@@ -446,8 +629,10 @@ static VALUE create_principal_from_entry(VALUE v_name, RUBY_KADM5* ptr, kadm5_pr
446
629
  char* mod_name;
447
630
  kerror = krb5_unparse_name(ptr->ctx, ent->mod_name, &mod_name);
448
631
 
449
- if(kerror)
632
+ if(kerror){
633
+ kadm5_free_principal_ent(ptr->handle, ent);
450
634
  rb_raise(cKadm5Exception, "krb5_unparse_name: %s", error_message(kerror));
635
+ }
451
636
 
452
637
  rb_iv_set(v_principal, "@mod_name", rb_str_new2(mod_name));
453
638
  krb5_free_unparsed_name(ptr->ctx, mod_name);
@@ -459,6 +644,8 @@ static VALUE create_principal_from_entry(VALUE v_name, RUBY_KADM5* ptr, kadm5_pr
459
644
  if(ent->policy)
460
645
  rb_iv_set(v_principal, "@policy", rb_str_new2(ent->policy));
461
646
 
647
+ kadm5_free_principal_ent(ptr->handle, ent);
648
+
462
649
  return v_principal;
463
650
  }
464
651
 
@@ -518,7 +705,6 @@ static VALUE rkadm5_find_principal(VALUE self, VALUE v_user){
518
705
  }
519
706
  else{
520
707
  v_principal = create_principal_from_entry(v_user, ptr, &ent);
521
- kadm5_free_principal_ent(ptr->handle, &ent);
522
708
  }
523
709
 
524
710
  return v_principal;
@@ -580,8 +766,6 @@ static VALUE rkadm5_get_principal(VALUE self, VALUE v_user){
580
766
 
581
767
  v_principal = create_principal_from_entry(v_user, ptr, &ent);
582
768
 
583
- kadm5_free_principal_ent(ptr->handle, &ent);
584
-
585
769
  return v_principal;
586
770
  }
587
771
 
@@ -594,28 +778,39 @@ static VALUE rkadm5_get_principal(VALUE self, VALUE v_user){
594
778
  * Example:
595
779
  *
596
780
  * # Using a Policy object
597
- * policy = Kerberos::Kadm5::Policy.new(:name => 'test', :min_length => 5)
781
+ * policy = Kerberos::Kadm5::Policy.new(name: 'test', min_length: 5)
598
782
  * kadm5.create_policy(policy)
599
783
  *
600
- * # Using a hash
601
- * kadm5.create_policy(:name => 'test', :min_length => 5)
784
+ * # Using keywords
785
+ * kadm5.create_policy(name: 'test', min_length: 5)
602
786
  */
603
- static VALUE rkadm5_create_policy(VALUE self, VALUE v_policy){
787
+ static VALUE rkadm5_create_policy(int argc, VALUE* argv, VALUE self){
604
788
  RUBY_KADM5* ptr;
605
789
  kadm5_ret_t kerror;
606
790
  kadm5_policy_ent_rec ent;
607
791
  long mask = KADM5_POLICY;
792
+ VALUE v_policy, v_kwargs;
608
793
  VALUE v_name, v_min_classes, v_min_life, v_max_life, v_min_length, v_history_num;
609
794
 
610
795
  TypedData_Get_Struct(self, RUBY_KADM5, &rkadm5_data_type, ptr);
611
796
 
612
- // Allow a hash or a Policy object
613
- if(rb_obj_is_kind_of(v_policy, rb_cHash)){
797
+ rb_scan_args(argc, argv, "01:", &v_policy, &v_kwargs);
798
+
799
+ if(!NIL_P(v_kwargs)){
614
800
  VALUE v_args[1];
615
- v_args[0] = v_policy;
616
- v_policy = rb_class_new_instance(1, v_args, cKadm5Policy);
801
+ if(!NIL_P(v_policy))
802
+ rb_raise(rb_eArgError, "provide a Policy object or keyword arguments, not both");
803
+
804
+ v_args[0] = v_kwargs;
805
+ v_policy = rb_class_new_instance_kw(1, v_args, cKadm5Policy, RB_PASS_KEYWORDS);
617
806
  }
618
807
 
808
+ if(NIL_P(v_policy))
809
+ rb_raise(rb_eArgError, "a Policy object or policy keywords are required");
810
+
811
+ if(!rb_obj_is_kind_of(v_policy, cKadm5Policy))
812
+ rb_raise(rb_eTypeError, "expected a Kerberos::Kadm5::Policy object or policy keywords");
813
+
619
814
  v_name = rb_iv_get(v_policy, "@policy");
620
815
  v_min_classes = rb_iv_get(v_policy, "@min_classes");
621
816
  v_min_length = rb_iv_get(v_policy, "@min_length");
@@ -723,16 +918,16 @@ static VALUE rkadm5_get_policy(VALUE self, VALUE v_name){
723
918
  VALUE v_arg[1];
724
919
  VALUE v_hash = rb_hash_new();
725
920
 
726
- rb_hash_aset(v_hash, rb_str_new2("name"), rb_str_new2(ent.policy));
727
- rb_hash_aset(v_hash, rb_str_new2("min_life"), LONG2FIX(ent.pw_min_life));
728
- rb_hash_aset(v_hash, rb_str_new2("max_life"), LONG2FIX(ent.pw_max_life));
729
- rb_hash_aset(v_hash, rb_str_new2("min_length"), LONG2FIX(ent.pw_min_length));
730
- rb_hash_aset(v_hash, rb_str_new2("min_classes"), LONG2FIX(ent.pw_min_classes));
731
- rb_hash_aset(v_hash, rb_str_new2("history_num"), LONG2FIX(ent.pw_history_num));
921
+ rb_hash_aset(v_hash, ID2SYM(rb_intern("name")), rb_str_new2(ent.policy));
922
+ rb_hash_aset(v_hash, ID2SYM(rb_intern("min_life")), LONG2FIX(ent.pw_min_life));
923
+ rb_hash_aset(v_hash, ID2SYM(rb_intern("max_life")), LONG2FIX(ent.pw_max_life));
924
+ rb_hash_aset(v_hash, ID2SYM(rb_intern("min_length")), LONG2FIX(ent.pw_min_length));
925
+ rb_hash_aset(v_hash, ID2SYM(rb_intern("min_classes")), LONG2FIX(ent.pw_min_classes));
926
+ rb_hash_aset(v_hash, ID2SYM(rb_intern("history_num")), LONG2FIX(ent.pw_history_num));
732
927
 
733
928
  v_arg[0] = v_hash;
734
929
 
735
- v_policy = rb_class_new_instance(1, v_arg, cKadm5Policy);
930
+ v_policy = rb_class_new_instance_kw(1, v_arg, cKadm5Policy, RB_PASS_KEYWORDS);
736
931
 
737
932
  kadm5_free_policy_ent(ptr->handle, &ent);
738
933
  }
@@ -780,16 +975,16 @@ static VALUE rkadm5_find_policy(VALUE self, VALUE v_name){
780
975
  VALUE v_arg[1];
781
976
  VALUE v_hash = rb_hash_new();
782
977
 
783
- rb_hash_aset(v_hash, rb_str_new2("name"), rb_str_new2(ent.policy));
784
- rb_hash_aset(v_hash, rb_str_new2("min_life"), LONG2FIX(ent.pw_min_life));
785
- rb_hash_aset(v_hash, rb_str_new2("max_life"), LONG2FIX(ent.pw_max_life));
786
- rb_hash_aset(v_hash, rb_str_new2("min_length"), LONG2FIX(ent.pw_min_length));
787
- rb_hash_aset(v_hash, rb_str_new2("min_classes"), LONG2FIX(ent.pw_min_classes));
788
- rb_hash_aset(v_hash, rb_str_new2("history_num"), LONG2FIX(ent.pw_history_num));
978
+ rb_hash_aset(v_hash, ID2SYM(rb_intern("name")), rb_str_new2(ent.policy));
979
+ rb_hash_aset(v_hash, ID2SYM(rb_intern("min_life")), LONG2FIX(ent.pw_min_life));
980
+ rb_hash_aset(v_hash, ID2SYM(rb_intern("max_life")), LONG2FIX(ent.pw_max_life));
981
+ rb_hash_aset(v_hash, ID2SYM(rb_intern("min_length")), LONG2FIX(ent.pw_min_length));
982
+ rb_hash_aset(v_hash, ID2SYM(rb_intern("min_classes")), LONG2FIX(ent.pw_min_classes));
983
+ rb_hash_aset(v_hash, ID2SYM(rb_intern("history_num")), LONG2FIX(ent.pw_history_num));
789
984
 
790
985
  v_arg[0] = v_hash;
791
986
 
792
- v_policy = rb_class_new_instance(1, v_arg, cKadm5Policy);
987
+ v_policy = rb_class_new_instance_kw(1, v_arg, cKadm5Policy, RB_PASS_KEYWORDS);
793
988
 
794
989
  kadm5_free_policy_ent(ptr->handle, &ent);
795
990
  }
@@ -1039,7 +1234,7 @@ char** parse_db_args(VALUE v_db_args){
1039
1234
  switch(TYPE(v_db_args)){
1040
1235
  case T_STRING:
1041
1236
  db_args = (char **) malloc(2 * sizeof(char *));
1042
- db_args[0] = StringValueCStr(v_db_args);
1237
+ db_args[0] = strdup(StringValueCStr(v_db_args));
1043
1238
  db_args[1] = NULL;
1044
1239
  break;
1045
1240
  case T_ARRAY:
@@ -1049,7 +1244,7 @@ char** parse_db_args(VALUE v_db_args){
1049
1244
  for(long i = 0; i < array_length; ++i){
1050
1245
  VALUE elem = rb_ary_entry(v_db_args, i);
1051
1246
  Check_Type(elem, T_STRING);
1052
- db_args[i] = StringValueCStr(elem);
1247
+ db_args[i] = strdup(StringValueCStr(elem));
1053
1248
  }
1054
1249
  db_args[array_length] = NULL;
1055
1250
  break;
@@ -1062,6 +1257,16 @@ char** parse_db_args(VALUE v_db_args){
1062
1257
  return db_args;
1063
1258
  }
1064
1259
 
1260
+ /**
1261
+ * Free a NULL-terminated array of strings returned by parse_db_args.
1262
+ */
1263
+ static void free_db_args(char** db_args){
1264
+ if(!db_args) return;
1265
+ for(int i = 0; db_args[i] != NULL; i++)
1266
+ free(db_args[i]);
1267
+ free(db_args);
1268
+ }
1269
+
1065
1270
  /**
1066
1271
  * Add parsed db-args to principal entry
1067
1272
  */
@@ -1125,12 +1330,12 @@ void Init_kadm5(void){
1125
1330
 
1126
1331
  // Initialization Method
1127
1332
 
1128
- rb_define_method(cKadm5, "initialize", rkadm5_initialize, 1);
1333
+ rb_define_method(cKadm5, "initialize", rkadm5_initialize, -1);
1129
1334
 
1130
1335
  // Instance Methods
1131
1336
 
1132
1337
  rb_define_method(cKadm5, "close", rkadm5_close, 0);
1133
- rb_define_method(cKadm5, "create_policy", rkadm5_create_policy, 1);
1338
+ rb_define_method(cKadm5, "create_policy", rkadm5_create_policy, -1);
1134
1339
  rb_define_method(cKadm5, "create_principal", rkadm5_create_principal, -1);
1135
1340
  rb_define_method(cKadm5, "delete_policy", rkadm5_delete_policy, 1);
1136
1341
  rb_define_method(cKadm5, "delete_principal", rkadm5_delete_principal, 1);