rkerberos 0.2.3 → 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.
@@ -4,6 +4,13 @@ VALUE cKrb5Keytab, cKrb5KeytabException;
4
4
 
5
5
 
6
6
  // TypedData functions for RUBY_KRB5_KEYTAB
7
+ static void rkrb5_keytab_typed_mark(void *ptr) {
8
+ if (!ptr) return;
9
+ RUBY_KRB5_KEYTAB *kt = (RUBY_KRB5_KEYTAB *)ptr;
10
+ if (kt->rb_context != Qnil)
11
+ rb_gc_mark(kt->rb_context);
12
+ }
13
+
7
14
  void rkrb5_keytab_typed_free(void *ptr) {
8
15
  if (!ptr) return;
9
16
  RUBY_KRB5_KEYTAB *kt = (RUBY_KRB5_KEYTAB *)ptr;
@@ -11,7 +18,7 @@ void rkrb5_keytab_typed_free(void *ptr) {
11
18
  krb5_kt_close(kt->ctx, kt->keytab);
12
19
  if (kt->ctx)
13
20
  krb5_free_cred_contents(kt->ctx, &kt->creds);
14
- if (kt->ctx)
21
+ if (kt->ctx && kt->rb_context == Qnil)
15
22
  krb5_free_context(kt->ctx);
16
23
  free(kt);
17
24
  }
@@ -23,13 +30,14 @@ size_t rkrb5_keytab_typed_size(const void *ptr) {
23
30
  // Must NOT be static so it is exported
24
31
  const rb_data_type_t rkrb5_keytab_data_type = {
25
32
  "RUBY_KRB5_KEYTAB",
26
- {NULL, rkrb5_keytab_typed_free, rkrb5_keytab_typed_size,},
33
+ {rkrb5_keytab_typed_mark, rkrb5_keytab_typed_free, rkrb5_keytab_typed_size,},
27
34
  NULL, NULL, RUBY_TYPED_FREE_IMMEDIATELY
28
35
  };
29
36
 
30
37
  VALUE rkrb5_keytab_allocate(VALUE klass){
31
38
  RUBY_KRB5_KEYTAB* ptr = ALLOC(RUBY_KRB5_KEYTAB);
32
39
  memset(ptr, 0, sizeof(RUBY_KRB5_KEYTAB));
40
+ ptr->rb_context = Qnil;
33
41
  return TypedData_Wrap_Struct(klass, &rkrb5_keytab_data_type, ptr);
34
42
  }
35
43
 
@@ -167,109 +175,189 @@ static VALUE rkrb5_keytab_close(VALUE self){
167
175
  if(ptr->ctx)
168
176
  krb5_free_cred_contents(ptr->ctx, &ptr->creds);
169
177
 
170
- if(ptr->ctx)
178
+ if(ptr->ctx && ptr->rb_context == Qnil)
171
179
  krb5_free_context(ptr->ctx);
172
180
 
173
181
  ptr->ctx = NULL;
182
+ ptr->rb_context = Qnil;
174
183
 
175
184
  return Qtrue;
176
185
  }
177
186
 
178
187
  /*
179
- static VALUE rkrb5_keytab_remove_entry(int argc, VALUE* argv, VALUE self){
188
+ * call-seq:
189
+ * keytab.add_entry(principal:, password:, vno: 1, enctype: 18)
190
+ *
191
+ * Add a new entry to the keytab for the given +principal+ string.
192
+ *
193
+ * A key is derived from +password+ (and the principal-based salt) using
194
+ * +krb5_c_string_to_key+, so the resulting key will match what the KDC
195
+ * would generate for the same password.
196
+ *
197
+ * Options:
198
+ * principal:: A Kerberos principal name string (required).
199
+ * password:: The password used to derive the key (required).
200
+ * vno:: Key version number (default 1).
201
+ * enctype:: Encryption type as an integer (default 18,
202
+ * aes256-cts-hmac-sha1-96).
203
+ *
204
+ * Returns +self+.
205
+ */
206
+ static VALUE rkrb5_keytab_add_entry(int argc, VALUE* argv, VALUE self){
180
207
  RUBY_KRB5_KEYTAB* ptr;
181
208
  krb5_error_code kerror;
182
209
  krb5_keytab_entry entry;
183
- char* name;
184
- VALUE v_name, v_vno, v_enctype;
210
+ krb5_data pwd_data, salt;
211
+ VALUE v_opts, v_principal, v_password, v_vno, v_enctype;
212
+ ID kw_table[4] = { rb_intern("principal"), rb_intern("password"), rb_intern("vno"), rb_intern("enctype") };
213
+ VALUE kw_vals[4];
185
214
 
186
215
  TypedData_Get_Struct(self, RUBY_KRB5_KEYTAB, &rkrb5_keytab_data_type, ptr);
187
216
 
188
- rb_scan_args(argc, argv, "12", &v_name, &v_vno, &v_enctype);
217
+ if(!ptr->ctx)
218
+ rb_raise(cKrb5Exception, "no context has been established");
189
219
 
190
- Check_Type(v_name, T_STRING);
220
+ rb_scan_args(argc, argv, "0:", &v_opts);
191
221
 
192
- name = StringValueCStr(v_name);
222
+ if(NIL_P(v_opts))
223
+ v_opts = rb_hash_new();
193
224
 
194
- if(!ptr->ctx)
195
- rb_raise(cKrb5Exception, "no context has been established");
225
+ rb_get_kwargs(v_opts, kw_table, 2, 2, kw_vals);
226
+ v_principal = kw_vals[0];
227
+ v_password = kw_vals[1];
228
+ v_vno = kw_vals[2] == Qundef ? Qnil : kw_vals[2];
229
+ v_enctype = kw_vals[3] == Qundef ? Qnil : kw_vals[3];
230
+
231
+ Check_Type(v_principal, T_STRING);
232
+ Check_Type(v_password, T_STRING);
196
233
 
197
- kerror = krb5_parse_name(ptr->ctx, name, &entry.principal);
234
+ memset(&entry, 0, sizeof(entry));
235
+
236
+ kerror = krb5_parse_name(ptr->ctx, StringValueCStr(v_principal), &entry.principal);
198
237
 
199
238
  if(kerror)
200
239
  rb_raise(cKrb5Exception, "krb5_parse_name: %s", error_message(kerror));
201
240
 
202
- if(NIL_P(v_vno))
203
- entry.vno = 0;
204
- else
205
- entry.vno = NUM2INT(v_vno);
241
+ entry.vno = NIL_P(v_vno) ? 1 : NUM2INT(v_vno);
242
+ entry.key.enctype = NIL_P(v_enctype) ? ENCTYPE_AES256_CTS_HMAC_SHA1_96 : NUM2INT(v_enctype);
243
+ entry.timestamp = time(NULL);
206
244
 
207
- if(NIL_P(v_enctype))
208
- entry.key.enctype = 0;
209
- else
210
- entry.key.enctype = NUM2INT(v_enctype);
245
+ // Derive the salt from the principal
246
+ kerror = krb5_principal2salt(ptr->ctx, entry.principal, &salt);
211
247
 
212
- entry.key.length = 16;
248
+ if(kerror){
249
+ krb5_free_principal(ptr->ctx, entry.principal);
250
+ rb_raise(cKrb5Exception, "krb5_principal2salt: %s", error_message(kerror));
251
+ }
213
252
 
214
- kerror = krb5_kt_remove_entry(
215
- ptr->ctx,
216
- ptr->keytab,
217
- &entry
218
- );
253
+ // Derive key from password + salt
254
+ pwd_data.data = StringValuePtr(v_password);
255
+ pwd_data.length = (unsigned int)RSTRING_LEN(v_password);
256
+
257
+ kerror = krb5_c_string_to_key(ptr->ctx, entry.key.enctype, &pwd_data, &salt, &entry.key);
258
+
259
+ krb5_free_data_contents(ptr->ctx, &salt);
260
+
261
+ if(kerror){
262
+ krb5_free_principal(ptr->ctx, entry.principal);
263
+ rb_raise(cKrb5Exception, "krb5_c_string_to_key: %s", error_message(kerror));
264
+ }
265
+
266
+ kerror = krb5_kt_add_entry(ptr->ctx, ptr->keytab, &entry);
267
+
268
+ krb5_free_keyblock_contents(ptr->ctx, &entry.key);
269
+ krb5_free_principal(ptr->ctx, entry.principal);
219
270
 
220
271
  if(kerror)
221
- rb_raise(cKrb5KeytabException, "krb5_kt_remove_entry: %s", error_message(kerror));
272
+ rb_raise(cKrb5KeytabException, "krb5_kt_add_entry: %s", error_message(kerror));
222
273
 
223
274
  return self;
224
275
  }
225
276
 
226
- static VALUE rkrb5_keytab_add_entry(int argc, VALUE* argv, VALUE self){
277
+ /*
278
+ * call-seq:
279
+ * keytab.remove_entry(principal:, vno: 0, enctype: 0)
280
+ *
281
+ * Remove entries from the keytab that match the given +principal+ string,
282
+ * +vno+, and +enctype+.
283
+ *
284
+ * A +vno+ of 0 (the default) matches any version number.
285
+ * An +enctype+ of 0 (the default) matches any encryption type.
286
+ *
287
+ * When both +vno+ and +enctype+ are 0 (the defaults), all entries for
288
+ * the +principal+ are removed.
289
+ *
290
+ * Options:
291
+ * principal:: A Kerberos principal name string (required).
292
+ * vno:: Key version number to match, 0 for any (default 0).
293
+ * enctype:: Encryption type to match, 0 for any (default 0).
294
+ *
295
+ * Returns +self+.
296
+ */
297
+ static VALUE rkrb5_keytab_remove_entry(int argc, VALUE* argv, VALUE self){
227
298
  RUBY_KRB5_KEYTAB* ptr;
228
299
  krb5_error_code kerror;
229
- krb5_keytab_entry entry;
230
- char* name;
231
- VALUE v_name, v_vno, v_enctype;
300
+ krb5_keytab_entry found_entry;
301
+ krb5_principal match_princ;
302
+ krb5_kvno match_vno;
303
+ krb5_enctype match_enctype;
304
+ int removed = 0;
305
+ VALUE v_opts, v_principal, v_vno, v_enctype;
306
+ ID kw_table[3] = { rb_intern("principal"), rb_intern("vno"), rb_intern("enctype") };
307
+ VALUE kw_vals[3];
232
308
 
233
309
  TypedData_Get_Struct(self, RUBY_KRB5_KEYTAB, &rkrb5_keytab_data_type, ptr);
234
310
 
235
- rb_scan_args(argc, argv, "12", &v_name, &v_vno, &v_enctype);
311
+ if(!ptr->ctx)
312
+ rb_raise(cKrb5Exception, "no context has been established");
236
313
 
237
- Check_Type(v_name, T_STRING);
314
+ rb_scan_args(argc, argv, "0:", &v_opts);
238
315
 
239
- name = StringValueCStr(v_name);
316
+ if(NIL_P(v_opts))
317
+ v_opts = rb_hash_new();
240
318
 
241
- if(!ptr->ctx)
242
- rb_raise(cKrb5Exception, "no context has been established");
319
+ rb_get_kwargs(v_opts, kw_table, 1, 2, kw_vals);
320
+ v_principal = kw_vals[0];
321
+ v_vno = kw_vals[1] == Qundef ? Qnil : kw_vals[1];
322
+ v_enctype = kw_vals[2] == Qundef ? Qnil : kw_vals[2];
243
323
 
244
- kerror = krb5_parse_name(ptr->ctx, name, &entry.principal);
324
+ Check_Type(v_principal, T_STRING);
325
+
326
+ kerror = krb5_parse_name(ptr->ctx, StringValueCStr(v_principal), &match_princ);
245
327
 
246
328
  if(kerror)
247
329
  rb_raise(cKrb5Exception, "krb5_parse_name: %s", error_message(kerror));
248
330
 
249
- if(NIL_P(v_vno))
250
- entry.vno = 0;
251
- else
252
- entry.vno = NUM2INT(v_vno);
331
+ match_vno = NIL_P(v_vno) ? 0 : NUM2INT(v_vno);
332
+ match_enctype = NIL_P(v_enctype) ? 0 : NUM2INT(v_enctype);
253
333
 
254
- if(NIL_P(v_enctype))
255
- entry.key.enctype = 0;
256
- else
257
- entry.key.enctype = NUM2INT(v_enctype);
334
+ // Retrieve the full entry via krb5_kt_get_entry and then pass the
335
+ // complete struct to krb5_kt_remove_entry so all fields match exactly.
336
+ // Loop to remove every matching entry when vno/enctype are wildcards.
337
+ while(1){
338
+ kerror = krb5_kt_get_entry(
339
+ ptr->ctx, ptr->keytab, match_princ,
340
+ match_vno, match_enctype, &found_entry
341
+ );
258
342
 
259
- entry.key.length = 16;
343
+ if(kerror){
344
+ krb5_free_principal(ptr->ctx, match_princ);
345
+ if(removed)
346
+ return self;
347
+ rb_raise(cKrb5KeytabException, "krb5_kt_remove_entry: %s", error_message(kerror));
348
+ }
260
349
 
261
- kerror = krb5_kt_add_entry(
262
- ptr->ctx,
263
- ptr->keytab,
264
- &entry
265
- );
350
+ kerror = krb5_kt_remove_entry(ptr->ctx, ptr->keytab, &found_entry);
351
+ krb5_kt_free_entry(ptr->ctx, &found_entry);
266
352
 
267
- if(kerror)
268
- rb_raise(cKrb5KeytabException, "krb5_kt_add_entry: %s", error_message(kerror));
353
+ if(kerror){
354
+ krb5_free_principal(ptr->ctx, match_princ);
355
+ rb_raise(cKrb5KeytabException, "krb5_kt_remove_entry: %s", error_message(kerror));
356
+ }
269
357
 
270
- return self;
358
+ removed = 1;
359
+ }
271
360
  }
272
- */
273
361
 
274
362
  /*
275
363
  * call-seq:
@@ -430,7 +518,7 @@ static VALUE rkrb5_keytab_dup(VALUE self){
430
518
 
431
519
  /*
432
520
  * call-seq:
433
- * Kerberos::Krb5::Keytab.new(name = nil)
521
+ * Kerberos::Krb5::Keytab.new(name: nil, context: nil)
434
522
  *
435
523
  * Creates and returns a new Kerberos::Krb5::Keytab object. This initializes
436
524
  * the context and keytab for future method calls on that object.
@@ -439,28 +527,66 @@ static VALUE rkrb5_keytab_dup(VALUE self){
439
527
  * name is used. If a +name+ is provided it must be in the form 'type:residual'
440
528
  * where 'type' is a type known to the Kerberos library.
441
529
  *
530
+ * An optional +context+ keyword argument may be provided. If given, it must
531
+ * be a Kerberos::Krb5::Context object and will be used instead of creating
532
+ * a new context via krb5_init_context.
533
+ *
442
534
  * Examples:
443
535
  *
444
536
  * # Using the default keytab
445
537
  * keytab = Kerberos::Krb5::Keytab.new
446
538
  *
447
539
  * # Using an explicit keytab
448
- * keytab = Kerberos::Krb5::Keytab.new('FILE:/etc/krb5.keytab')
540
+ * keytab = Kerberos::Krb5::Keytab.new(name: 'FILE:/etc/krb5.keytab')
541
+ *
542
+ * # Using a custom context
543
+ * ctx = Kerberos::Krb5::Context.new
544
+ * keytab = Kerberos::Krb5::Keytab.new(name: 'FILE:/etc/krb5.keytab', context: ctx)
449
545
  */
450
546
  static VALUE rkrb5_keytab_initialize(int argc, VALUE* argv, VALUE self){
451
547
  RUBY_KRB5_KEYTAB* ptr;
452
548
  krb5_error_code kerror;
453
549
  char keytab_name[MAX_KEYTAB_NAME_LEN];
454
550
  VALUE v_keytab_name = Qnil;
551
+ VALUE v_opts = Qnil;
552
+ VALUE v_context = Qnil;
553
+ ID kw_table[2] = { rb_intern("name"), rb_intern("context") };
554
+ VALUE kw_vals[2];
455
555
 
456
556
  TypedData_Get_Struct(self, RUBY_KRB5_KEYTAB, &rkrb5_keytab_data_type, ptr);
457
557
 
458
- rb_scan_args(argc, argv, "01", &v_keytab_name);
558
+ rb_scan_args(argc, argv, "0:", &v_opts);
459
559
 
460
- kerror = krb5_init_context(&ptr->ctx);
560
+ if(NIL_P(v_opts))
561
+ v_opts = rb_hash_new();
461
562
 
462
- if(kerror)
463
- rb_raise(cKrb5Exception, "krb5_init_context: %s", error_message(kerror));
563
+ rb_get_kwargs(v_opts, kw_table, 0, 2, kw_vals);
564
+ v_keytab_name = kw_vals[0] == Qundef ? Qnil : kw_vals[0];
565
+ v_context = kw_vals[1] == Qundef ? Qnil : kw_vals[1];
566
+
567
+ // Initialize or borrow the context
568
+ if(!NIL_P(v_context)){
569
+ RUBY_KRB5_CONTEXT* ctx_ptr;
570
+
571
+ if(!rb_obj_is_kind_of(v_context, cKrb5Context))
572
+ rb_raise(rb_eTypeError, "context must be a Kerberos::Krb5::Context object");
573
+
574
+ TypedData_Get_Struct(v_context, RUBY_KRB5_CONTEXT, &rkrb5_context_data_type, ctx_ptr);
575
+
576
+ if(!ctx_ptr->ctx)
577
+ rb_raise(cKrb5Exception, "context is closed");
578
+
579
+ ptr->ctx = ctx_ptr->ctx;
580
+ ptr->rb_context = v_context;
581
+ }
582
+ else{
583
+ kerror = krb5_init_context(&ptr->ctx);
584
+
585
+ if(kerror)
586
+ rb_raise(cKrb5Exception, "krb5_init_context: %s", error_message(kerror));
587
+
588
+ ptr->rb_context = Qnil;
589
+ }
464
590
 
465
591
  // Use the default keytab name if one isn't provided.
466
592
  if(NIL_P(v_keytab_name)){
@@ -616,6 +742,26 @@ static VALUE rkrb5_s_keytab_foreach(int argc, VALUE* argv, VALUE klass){
616
742
  return Qnil;
617
743
  }
618
744
 
745
+ /*
746
+ * call-seq:
747
+ * keytab.have_content? -> true or false
748
+ *
749
+ * Returns true if the keytab exists and contains entries, false otherwise.
750
+ */
751
+ static VALUE rkrb5_keytab_have_content(VALUE self){
752
+ RUBY_KRB5_KEYTAB* ptr;
753
+ krb5_error_code kerror;
754
+
755
+ TypedData_Get_Struct(self, RUBY_KRB5_KEYTAB, &rkrb5_keytab_data_type, ptr);
756
+
757
+ if(!ptr->ctx)
758
+ rb_raise(cKrb5Exception, "no context has been established");
759
+
760
+ kerror = krb5_kt_have_content(ptr->ctx, ptr->keytab);
761
+
762
+ return kerror ? Qfalse : Qtrue;
763
+ }
764
+
619
765
  void Init_keytab(void){
620
766
  /* The Kerberos::Krb5::Keytab class encapsulates a Kerberos keytab. */
621
767
  cKrb5Keytab = rb_define_class_under(cKrb5, "Keytab", rb_cObject);
@@ -644,11 +790,11 @@ void Init_keytab(void){
644
790
  rb_define_method(cKrb5Keytab, "keytab_name", rkrb5_keytab_get_name, 0);
645
791
  rb_define_method(cKrb5Keytab, "keytab_type", rkrb5_keytab_get_type, 0);
646
792
  rb_define_method(cKrb5Keytab, "dup", rkrb5_keytab_dup, 0);
793
+ rb_define_method(cKrb5Keytab, "have_content?", rkrb5_keytab_have_content, 0);
647
794
  rb_define_alias(cKrb5Keytab, "clone", "dup");
648
795
 
649
- // TODO: Move these into Kadm5 and/or figure out how to set the vno properly.
650
- // rb_define_method(cKrb5Keytab, "add_entry", rkrb5_keytab_add_entry, -1);
651
- // rb_define_method(cKrb5Keytab, "remove_entry", rkrb5_keytab_remove_entry, -1);
796
+ rb_define_method(cKrb5Keytab, "add_entry", rkrb5_keytab_add_entry, -1);
797
+ rb_define_method(cKrb5Keytab, "remove_entry", rkrb5_keytab_remove_entry, -1);
652
798
 
653
799
  // Accessors
654
800
 
@@ -32,12 +32,12 @@ static VALUE rkadm5_policy_allocate(VALUE klass){
32
32
 
33
33
  /*
34
34
  * call-seq:
35
- * Kerberos::Kadm5::Policy.new(options)
35
+ * Kerberos::Kadm5::Policy.new(name:, min_life: nil, max_life: nil,
36
+ * min_length: nil, min_classes: nil, history_num: nil)
36
37
  *
37
- * Returns a new policy object using +options+ you choose to pass, where
38
- * the +options+ argument is a hash. This does NOT actually create the policy
39
- * object within Kerberos. To do that pass your Policy object to the
40
- * Kadm5.create_policy method.
38
+ * Returns a new policy object using the keyword arguments you choose to pass.
39
+ * This does NOT actually create the policy within Kerberos. To do that pass
40
+ * your Policy object to the Kadm5.create_policy method.
41
41
  *
42
42
  * The possible options are:
43
43
  *
@@ -50,32 +50,34 @@ static VALUE rkadm5_policy_allocate(VALUE klass){
50
50
  *
51
51
  * If you do not provide a :name then an ArgumentError will be raised.
52
52
  */
53
- static VALUE rkadm5_policy_init(VALUE self, VALUE v_options){
53
+ static VALUE rkadm5_policy_init(int argc, VALUE* argv, VALUE self){
54
54
  RUBY_KADM5_POLICY* ptr;
55
+ VALUE v_options;
55
56
  VALUE v_name, v_minlife, v_maxlife, v_minlength;
56
57
  VALUE v_minclasses, v_historynum;
58
+ ID kw_table[6] = {
59
+ rb_intern("name"), rb_intern("min_life"), rb_intern("max_life"),
60
+ rb_intern("min_length"), rb_intern("min_classes"), rb_intern("history_num")
61
+ };
62
+ VALUE kw_vals[6];
57
63
 
58
64
  TypedData_Get_Struct(self, RUBY_KADM5_POLICY, &rkadm5_policy_data_type, ptr);
59
65
 
60
- Check_Type(v_options, T_HASH);
66
+ rb_scan_args(argc, argv, "0:", &v_options);
61
67
 
62
- if(RTEST(rb_funcall(v_options, rb_intern("empty?"), 0)))
63
- rb_raise(rb_eArgError, "no policy options provided");
68
+ if(NIL_P(v_options))
69
+ v_options = rb_hash_new();
64
70
 
65
- v_name = rb_hash_aref2(v_options, rb_str_new_cstr("name"));
66
- v_minlife = rb_hash_aref2(v_options, rb_str_new_cstr("min_life"));
67
- v_maxlife = rb_hash_aref2(v_options, rb_str_new_cstr("max_life"));
68
- v_minlength = rb_hash_aref2(v_options, rb_str_new_cstr("min_length"));
69
- v_minclasses = rb_hash_aref2(v_options, rb_str_new_cstr("min_classes"));
70
- v_historynum = rb_hash_aref2(v_options, rb_str_new_cstr("history_num"));
71
+ rb_get_kwargs(v_options, kw_table, 1, 5, kw_vals);
72
+ v_name = kw_vals[0];
73
+ v_minlife = kw_vals[1] == Qundef ? Qnil : kw_vals[1];
74
+ v_maxlife = kw_vals[2] == Qundef ? Qnil : kw_vals[2];
75
+ v_minlength = kw_vals[3] == Qundef ? Qnil : kw_vals[3];
76
+ v_minclasses = kw_vals[4] == Qundef ? Qnil : kw_vals[4];
77
+ v_historynum = kw_vals[5] == Qundef ? Qnil : kw_vals[5];
71
78
 
72
- if(NIL_P(v_name)){
73
- rb_raise(rb_eArgError, "name policy option is mandatory");
74
- }
75
- else{
76
- ptr->policy.policy = StringValueCStr(v_name);
77
- rb_iv_set(self, "@policy", v_name);
78
- }
79
+ ptr->policy.policy = StringValueCStr(v_name);
80
+ rb_iv_set(self, "@policy", v_name);
79
81
 
80
82
  if(!NIL_P(v_minlife)){
81
83
  ptr->policy.pw_min_life = NUM2LONG(v_minlife);
@@ -171,7 +173,7 @@ void Init_policy(void){
171
173
 
172
174
  // Initialization Function
173
175
 
174
- rb_define_method(cKadm5Policy, "initialize", rkadm5_policy_init, 1);
176
+ rb_define_method(cKadm5Policy, "initialize", rkadm5_policy_init, -1);
175
177
 
176
178
  // Instance methods
177
179
 
@@ -4,12 +4,19 @@ VALUE cKrb5Principal;
4
4
 
5
5
 
6
6
  // TypedData functions for RUBY_KRB5_PRINC
7
+ static void rkrb5_princ_typed_mark(void *ptr) {
8
+ if (!ptr) return;
9
+ RUBY_KRB5_PRINC *p = (RUBY_KRB5_PRINC *)ptr;
10
+ if (p->rb_context != Qnil)
11
+ rb_gc_mark(p->rb_context);
12
+ }
13
+
7
14
  static void rkrb5_princ_typed_free(void *ptr) {
8
15
  if (!ptr) return;
9
16
  RUBY_KRB5_PRINC *p = (RUBY_KRB5_PRINC *)ptr;
10
17
  if (p->principal)
11
18
  krb5_free_principal(p->ctx, p->principal);
12
- if (p->ctx)
19
+ if (p->ctx && p->rb_context == Qnil)
13
20
  krb5_free_context(p->ctx);
14
21
  free(p);
15
22
  }
@@ -20,7 +27,7 @@ static size_t rkrb5_princ_typed_size(const void *ptr) {
20
27
 
21
28
  static const rb_data_type_t rkrb5_princ_data_type = {
22
29
  "RUBY_KRB5_PRINC",
23
- {NULL, rkrb5_princ_typed_free, rkrb5_princ_typed_size,},
30
+ {rkrb5_princ_typed_mark, rkrb5_princ_typed_free, rkrb5_princ_typed_size,},
24
31
  NULL, NULL, RUBY_TYPED_FREE_IMMEDIATELY
25
32
  };
26
33
 
@@ -28,34 +35,78 @@ static const rb_data_type_t rkrb5_princ_data_type = {
28
35
  static VALUE rkrb5_princ_allocate(VALUE klass){
29
36
  RUBY_KRB5_PRINC* ptr = ALLOC(RUBY_KRB5_PRINC);
30
37
  memset(ptr, 0, sizeof(RUBY_KRB5_PRINC));
38
+ ptr->rb_context = Qnil;
31
39
  return TypedData_Wrap_Struct(klass, &rkrb5_princ_data_type, ptr);
32
40
  }
33
41
 
34
42
  /*
35
43
  * call-seq:
36
- * Kerberos::Krb5::Principal.new(name)
44
+ * Kerberos::Krb5::Principal.new(name: nil, context: nil)
37
45
  *
38
46
  * Creates and returns a new Krb5::Principal object. If a block is provided
39
47
  * then it yields itself.
40
48
  *
49
+ * A principal +name+ may be provided as a keyword argument. If not provided
50
+ * or nil, the principal attribute will be nil.
51
+ *
52
+ * An optional +context+ keyword argument may be provided. If given, it must
53
+ * be a Kerberos::Krb5::Context object and will be used instead of creating
54
+ * a new context via krb5_init_context.
55
+ *
41
56
  * Example:
42
57
  *
43
- * principal1 = Kerberos::Krb5::Principal.new('Jon')
58
+ * principal1 = Kerberos::Krb5::Principal.new(name: 'Jon')
44
59
  *
45
- * principal2 = Kerberos::Krb5::Principal.new('Jon') do |pr|
60
+ * principal2 = Kerberos::Krb5::Principal.new(name: 'Jon') do |pr|
46
61
  * pr.expire_time = Time.now + 20000
47
62
  * end
63
+ *
64
+ * ctx = Kerberos::Krb5::Context.new
65
+ * principal3 = Kerberos::Krb5::Principal.new(name: 'Jon', context: ctx)
48
66
  */
49
- static VALUE rkrb5_princ_initialize(VALUE self, VALUE v_name){
67
+ static VALUE rkrb5_princ_initialize(int argc, VALUE* argv, VALUE self){
50
68
  RUBY_KRB5_PRINC* ptr;
51
69
  krb5_error_code kerror;
70
+ VALUE v_opts = Qnil;
71
+ VALUE v_name = Qnil;
72
+ VALUE v_context = Qnil;
73
+ ID kw_table[2] = { rb_intern("name"), rb_intern("context") };
74
+ VALUE kw_vals[2];
52
75
 
53
76
  TypedData_Get_Struct(self, RUBY_KRB5_PRINC, &rkrb5_princ_data_type, ptr);
54
77
 
55
- kerror = krb5_init_context(&ptr->ctx);
78
+ rb_scan_args(argc, argv, "0:", &v_opts);
79
+
80
+ if(NIL_P(v_opts))
81
+ v_opts = rb_hash_new();
82
+
83
+ rb_get_kwargs(v_opts, kw_table, 0, 2, kw_vals);
84
+ v_name = kw_vals[0] == Qundef ? Qnil : kw_vals[0];
85
+ v_context = kw_vals[1] == Qundef ? Qnil : kw_vals[1];
86
+
87
+ // Initialize or borrow the context
88
+ if(!NIL_P(v_context)){
89
+ RUBY_KRB5_CONTEXT* ctx_ptr;
90
+
91
+ if(!rb_obj_is_kind_of(v_context, cKrb5Context))
92
+ rb_raise(rb_eTypeError, "context must be a Kerberos::Krb5::Context object");
93
+
94
+ TypedData_Get_Struct(v_context, RUBY_KRB5_CONTEXT, &rkrb5_context_data_type, ctx_ptr);
56
95
 
57
- if(kerror)
58
- rb_raise(cKrb5Exception, "krb5_init_context failed: %s", error_message(kerror));
96
+ if(!ctx_ptr->ctx)
97
+ rb_raise(cKrb5Exception, "context is closed");
98
+
99
+ ptr->ctx = ctx_ptr->ctx;
100
+ ptr->rb_context = v_context;
101
+ }
102
+ else{
103
+ kerror = krb5_init_context(&ptr->ctx);
104
+
105
+ if(kerror)
106
+ rb_raise(cKrb5Exception, "krb5_init_context failed: %s", error_message(kerror));
107
+
108
+ ptr->rb_context = Qnil;
109
+ }
59
110
 
60
111
  if(NIL_P(v_name)){
61
112
  rb_iv_set(self, "@principal", Qnil);
@@ -233,6 +284,51 @@ static VALUE rkrb5_princ_inspect(VALUE self){
233
284
  return v_str;
234
285
  }
235
286
 
287
+ /*
288
+ * call-seq:
289
+ * principal.principal_type -> Integer
290
+ *
291
+ * Returns the type of the principal as an integer, e.g.
292
+ * KRB5_NT_PRINCIPAL (1), KRB5_NT_SRV_HST (2), etc.
293
+ */
294
+ static VALUE rkrb5_princ_get_type(VALUE self){
295
+ RUBY_KRB5_PRINC* ptr;
296
+
297
+ TypedData_Get_Struct(self, RUBY_KRB5_PRINC, &rkrb5_princ_data_type, ptr);
298
+
299
+ if(!ptr->principal)
300
+ rb_raise(cKrb5Exception, "no principal has been established");
301
+
302
+ return INT2FIX(krb5_princ_type(ptr->ctx, ptr->principal));
303
+ }
304
+
305
+ /*
306
+ * call-seq:
307
+ * principal.components -> Array
308
+ *
309
+ * Returns an array of the component strings that make up this principal
310
+ * name (excluding the realm). For example, "admin/instance@REALM" would
311
+ * return ["admin", "instance"].
312
+ */
313
+ static VALUE rkrb5_princ_components(VALUE self){
314
+ RUBY_KRB5_PRINC* ptr;
315
+
316
+ TypedData_Get_Struct(self, RUBY_KRB5_PRINC, &rkrb5_princ_data_type, ptr);
317
+
318
+ if(!ptr->principal)
319
+ rb_raise(cKrb5Exception, "no principal has been established");
320
+
321
+ int n = krb5_princ_size(ptr->ctx, ptr->principal);
322
+ VALUE v_array = rb_ary_new_capa(n);
323
+
324
+ for(int i = 0; i < n; i++){
325
+ krb5_data* component = krb5_princ_component(ptr->ctx, ptr->principal, i);
326
+ rb_ary_push(v_array, rb_str_new(component->data, component->length));
327
+ }
328
+
329
+ return v_array;
330
+ }
331
+
236
332
  void Init_principal(void){
237
333
  /* The Kerberos::Krb5::Principal class encapsulates a Kerberos principal. */
238
334
  cKrb5Principal = rb_define_class_under(cKrb5, "Principal", rb_cObject);
@@ -243,7 +339,7 @@ void Init_principal(void){
243
339
 
244
340
  // Constructor
245
341
 
246
- rb_define_method(cKrb5Principal, "initialize", rkrb5_princ_initialize, 1);
342
+ rb_define_method(cKrb5Principal, "initialize", rkrb5_princ_initialize, -1);
247
343
 
248
344
  // Instance Methods
249
345
 
@@ -251,6 +347,8 @@ void Init_principal(void){
251
347
  rb_define_method(cKrb5Principal, "realm", rkrb5_princ_get_realm, 0);
252
348
  rb_define_method(cKrb5Principal, "realm=", rkrb5_princ_set_realm, 1);
253
349
  rb_define_method(cKrb5Principal, "==", rkrb5_princ_equal, 1);
350
+ rb_define_method(cKrb5Principal, "principal_type", rkrb5_princ_get_type, 0);
351
+ rb_define_method(cKrb5Principal, "components", rkrb5_princ_components, 0);
254
352
 
255
353
  // Attributes
256
354