rkerberos 0.2.3 → 0.3.1

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.
@@ -25,6 +25,13 @@ VALUE rb_hash_aref2(VALUE v_hash, VALUE v_key){
25
25
 
26
26
 
27
27
  // TypedData functions for RUBY_KRB5
28
+ static void rkrb5_typed_mark(void *ptr) {
29
+ if (!ptr) return;
30
+ RUBY_KRB5 *r = (RUBY_KRB5 *)ptr;
31
+ if (r->rb_context != Qnil)
32
+ rb_gc_mark(r->rb_context);
33
+ }
34
+
28
35
  static void rkrb5_typed_free(void *ptr) {
29
36
  if (!ptr) return;
30
37
  RUBY_KRB5 *r = (RUBY_KRB5 *)ptr;
@@ -34,8 +41,10 @@ static void rkrb5_typed_free(void *ptr) {
34
41
  krb5_free_cred_contents(r->ctx, &r->creds);
35
42
  if (r->princ)
36
43
  krb5_free_principal(r->ctx, r->princ);
37
- if (r->ctx)
44
+ if (r->ctx && r->rb_context == Qnil)
38
45
  krb5_free_context(r->ctx);
46
+ else if (r->rb_context != Qnil)
47
+ rkrb5_context_release(r->rb_context);
39
48
  free(r);
40
49
  }
41
50
 
@@ -43,9 +52,9 @@ static size_t rkrb5_typed_size(const void *ptr) {
43
52
  return sizeof(RUBY_KRB5);
44
53
  }
45
54
 
46
- static const rb_data_type_t rkrb5_data_type = {
55
+ const rb_data_type_t rkrb5_data_type = {
47
56
  "RUBY_KRB5",
48
- {NULL, rkrb5_typed_free, rkrb5_typed_size,},
57
+ {rkrb5_typed_mark, rkrb5_typed_free, rkrb5_typed_size,},
49
58
  NULL, NULL, RUBY_TYPED_FREE_IMMEDIATELY
50
59
  };
51
60
 
@@ -53,26 +62,53 @@ static const rb_data_type_t rkrb5_data_type = {
53
62
  static VALUE rkrb5_allocate(VALUE klass){
54
63
  RUBY_KRB5* ptr = ALLOC(RUBY_KRB5);
55
64
  memset(ptr, 0, sizeof(RUBY_KRB5));
65
+ ptr->rb_context = Qnil;
56
66
  return TypedData_Wrap_Struct(klass, &rkrb5_data_type, ptr);
57
67
  }
58
68
 
59
69
  /*
60
70
  * call-seq:
61
- * Kerberos::Krb5.new
71
+ * Kerberos::Krb5.new(context: nil)
62
72
  *
63
73
  * Creates and returns a new Kerberos::Krb5 object. This initializes the
64
74
  * context for future method calls on that object.
75
+ *
76
+ * If a +context+ keyword argument is provided, it must be a
77
+ * Kerberos::Krb5::Context object. The context will be borrowed rather than
78
+ * creating a new one internally via krb5_init_context. The caller is
79
+ * retained for the lifetime of this Krb5 instance. The Context cannot be
80
+ * closed normally until this object has been closed.
81
+ *
82
+ * A block form is also supported. If a block is given, the object is
83
+ * yielded to the block and automatically closed when the block returns.
65
84
  */
66
- static VALUE rkrb5_initialize(VALUE self){
85
+ static VALUE rkrb5_initialize(int argc, VALUE* argv, VALUE self){
67
86
  RUBY_KRB5* ptr;
87
+ VALUE v_opts, v_context;
88
+ ID kw_table[1] = { rb_intern("context") };
89
+ VALUE kw_vals[1];
68
90
  krb5_error_code kerror;
69
91
 
70
92
  TypedData_Get_Struct(self, RUBY_KRB5, &rkrb5_data_type, ptr);
71
93
 
72
- kerror = krb5_init_context(&ptr->ctx);
94
+ rb_scan_args(argc, argv, "0:", &v_opts);
73
95
 
74
- if(kerror)
75
- rb_raise(cKrb5Exception, "krb5_init_context: %s", error_message(kerror));
96
+ if(NIL_P(v_opts))
97
+ v_opts = rb_hash_new();
98
+
99
+ rb_get_kwargs(v_opts, kw_table, 0, 1, kw_vals);
100
+ v_context = kw_vals[0] == Qundef ? Qnil : kw_vals[0];
101
+
102
+ if(!NIL_P(v_context)){
103
+ ptr->ctx = rkrb5_context_borrow(v_context);
104
+ ptr->rb_context = v_context;
105
+ }
106
+ else{
107
+ kerror = krb5_init_context(&ptr->ctx);
108
+
109
+ if(kerror)
110
+ rb_raise(cKrb5Exception, "krb5_init_context: %s", error_message(kerror));
111
+ }
76
112
 
77
113
  if(rb_block_given_p()){
78
114
  rb_ensure(rb_yield, self, rkrb5_close, self);
@@ -95,6 +131,9 @@ static VALUE rkrb5_get_default_realm(VALUE self){
95
131
 
96
132
  TypedData_Get_Struct(self, RUBY_KRB5, &rkrb5_data_type, ptr);
97
133
 
134
+ if(!ptr->ctx)
135
+ rb_raise(cKrb5Exception, "no context has been established");
136
+
98
137
  kerror = krb5_get_default_realm(ptr->ctx, &realm);
99
138
 
100
139
  if(kerror)
@@ -121,6 +160,9 @@ static VALUE rkrb5_set_default_realm(int argc, VALUE* argv, VALUE self){
121
160
 
122
161
  TypedData_Get_Struct(self, RUBY_KRB5, &rkrb5_data_type, ptr);
123
162
 
163
+ if(!ptr->ctx)
164
+ rb_raise(cKrb5Exception, "no context has been established");
165
+
124
166
  rb_scan_args(argc, argv, "01", &v_realm);
125
167
 
126
168
  if(NIL_P(v_realm)){
@@ -140,7 +182,7 @@ static VALUE rkrb5_set_default_realm(int argc, VALUE* argv, VALUE self){
140
182
  }
141
183
 
142
184
  /* call-seq:
143
- * krb5.get_init_creds_keytab(principal = nil, keytab = nil, service = nil, ccache = nil)
185
+ * krb5.get_init_creds_keytab(principal: nil, keytab: nil, service: nil, ccache: nil)
144
186
  *
145
187
  * Acquire credentials for +principal+ from +keytab+ using +service+. If
146
188
  * no principal is specified, then a principal is derived from the service
@@ -154,7 +196,10 @@ static VALUE rkrb5_set_default_realm(int argc, VALUE* argv, VALUE self){
154
196
  */
155
197
  static VALUE rkrb5_get_init_creds_keytab(int argc, VALUE* argv, VALUE self){
156
198
  RUBY_KRB5* ptr;
157
- VALUE v_user, v_keytab_name, v_service, v_ccache;
199
+ VALUE v_user = Qnil, v_keytab_name = Qnil, v_service = Qnil, v_ccache = Qnil;
200
+ VALUE v_kwargs = Qnil;
201
+ ID kw_table[4] = { rb_intern("principal"), rb_intern("keytab"), rb_intern("service"), rb_intern("ccache") };
202
+ VALUE kw_vals[4];
158
203
  char* user;
159
204
  char* service;
160
205
  char keytab_name[MAX_KEYTAB_NAME_LEN];
@@ -181,7 +226,16 @@ static VALUE rkrb5_get_init_creds_keytab(int argc, VALUE* argv, VALUE self){
181
226
  krb5_free_cred_contents(ptr->ctx, &ptr->creds);
182
227
  memset(&ptr->creds, 0, sizeof(ptr->creds));
183
228
 
184
- rb_scan_args(argc, argv, "04", &v_user, &v_keytab_name, &v_service, &v_ccache);
229
+ rb_scan_args(argc, argv, "0:", &v_kwargs);
230
+
231
+ if(NIL_P(v_kwargs))
232
+ v_kwargs = rb_hash_new();
233
+
234
+ rb_get_kwargs(v_kwargs, kw_table, 0, 4, kw_vals);
235
+ if(kw_vals[0] != Qundef) v_user = kw_vals[0];
236
+ if(kw_vals[1] != Qundef) v_keytab_name = kw_vals[1];
237
+ if(kw_vals[2] != Qundef) v_service = kw_vals[2];
238
+ if(kw_vals[3] != Qundef) v_ccache = kw_vals[3];
185
239
 
186
240
  // Validate argument types before allocating opt, so type errors don't leak it.
187
241
  if(!NIL_P(v_user))
@@ -261,6 +315,11 @@ static VALUE rkrb5_get_init_creds_keytab(int argc, VALUE* argv, VALUE self){
261
315
  RUBY_KRB5_CCACHE* ccptr;
262
316
  TypedData_Get_Struct(v_ccache, RUBY_KRB5_CCACHE, &rkrb5_ccache_data_type, ccptr);
263
317
 
318
+ if(!ccptr->ctx || !ccptr->ccache){
319
+ krb5_get_init_creds_opt_free(ptr->ctx, opt);
320
+ rb_raise(cKrb5Exception, "credentials cache is closed or destroyed");
321
+ }
322
+
264
323
  kerror = krb5_get_init_creds_opt_set_out_ccache(ptr->ctx, opt, ccptr->ccache);
265
324
  if(kerror) {
266
325
  krb5_get_init_creds_opt_free(ptr->ctx, opt);
@@ -300,7 +359,7 @@ static VALUE rkrb5_get_init_creds_keytab(int argc, VALUE* argv, VALUE self){
300
359
  *
301
360
  * Example:
302
361
  *
303
- * krb5.get_init_creds_password('foo', 'XXXXXX') # Authenticate 'foo' user
362
+ * krb5.get_init_creds_password(principal: 'foo', password: 'XXXXXX') # Authenticate 'foo' user
304
363
  * krb5.change_password('XXXXXX', 'YYYYYY') # Change password for 'foo'
305
364
  */
306
365
  static VALUE rkrb5_change_password(VALUE self, VALUE v_old, VALUE v_new){
@@ -328,6 +387,9 @@ static VALUE rkrb5_change_password(VALUE self, VALUE v_old, VALUE v_new){
328
387
  if(!ptr->princ)
329
388
  rb_raise(cKrb5Exception, "no principal has been established");
330
389
 
390
+ memset(&result_string, 0, sizeof(result_string));
391
+ memset(&pw_result_string, 0, sizeof(pw_result_string));
392
+
331
393
  krb5_free_cred_contents(ptr->ctx, &ptr->creds);
332
394
  memset(&ptr->creds, 0, sizeof(ptr->creds));
333
395
 
@@ -378,19 +440,26 @@ static VALUE rkrb5_change_password(VALUE self, VALUE v_old, VALUE v_new){
378
440
 
379
441
  /*
380
442
  * call-seq:
381
- * krb5.get_init_creds_password(user, password, service = nil)
443
+ * krb5.get_init_creds_password(principal: nil, password: nil, service: nil, ccache: nil)
382
444
  *
383
- * Authenticates the credentials of +user+ using +password+ against +service+,
445
+ * Authenticates the credentials of +principal+ using +password+ against +service+,
384
446
  * and has the effect of setting the principal and context internally. This method
385
447
  * must typically be called before using other methods.
448
+ *
449
+ * When +ccache+ is given, the acquired credentials will be written into the
450
+ * provided Kerberos::Krb5::CredentialsCache.
386
451
  */
387
452
  static VALUE rkrb5_get_init_creds_passwd(int argc, VALUE* argv, VALUE self){
388
453
  RUBY_KRB5* ptr;
389
- VALUE v_user, v_pass, v_service;
454
+ VALUE v_user = Qnil, v_pass = Qnil, v_service = Qnil, v_ccache = Qnil;
455
+ VALUE v_kwargs = Qnil;
456
+ ID kw_table[4] = { rb_intern("principal"), rb_intern("password"), rb_intern("service"), rb_intern("ccache") };
457
+ VALUE kw_vals[4];
390
458
  char* user;
391
459
  char* pass;
392
460
  char* service;
393
461
  krb5_error_code kerror;
462
+ krb5_get_init_creds_opt* opt = NULL;
394
463
 
395
464
  TypedData_Get_Struct(self, RUBY_KRB5, &rkrb5_data_type, ptr);
396
465
 
@@ -406,7 +475,19 @@ static VALUE rkrb5_get_init_creds_passwd(int argc, VALUE* argv, VALUE self){
406
475
  krb5_free_cred_contents(ptr->ctx, &ptr->creds);
407
476
  memset(&ptr->creds, 0, sizeof(ptr->creds));
408
477
 
409
- rb_scan_args(argc, argv, "21", &v_user, &v_pass, &v_service);
478
+ rb_scan_args(argc, argv, "0:", &v_kwargs);
479
+
480
+ if(NIL_P(v_kwargs))
481
+ v_kwargs = rb_hash_new();
482
+
483
+ rb_get_kwargs(v_kwargs, kw_table, 0, 4, kw_vals);
484
+ if(kw_vals[0] != Qundef) v_user = kw_vals[0];
485
+ if(kw_vals[1] != Qundef) v_pass = kw_vals[1];
486
+ if(kw_vals[2] != Qundef) v_service = kw_vals[2];
487
+ if(kw_vals[3] != Qundef) v_ccache = kw_vals[3];
488
+
489
+ if(NIL_P(v_user) || NIL_P(v_pass))
490
+ rb_raise(rb_eArgError, "principal and password are required");
410
491
 
411
492
  Check_Type(v_user, T_STRING);
412
493
  Check_Type(v_pass, T_STRING);
@@ -426,6 +507,24 @@ static VALUE rkrb5_get_init_creds_passwd(int argc, VALUE* argv, VALUE self){
426
507
  if(kerror)
427
508
  rb_raise(cKrb5Exception, "krb5_parse_name: %s", error_message(kerror));
428
509
 
510
+ if(!NIL_P(v_ccache)){
511
+ RUBY_KRB5_CCACHE* ccptr;
512
+ TypedData_Get_Struct(v_ccache, RUBY_KRB5_CCACHE, &rkrb5_ccache_data_type, ccptr);
513
+
514
+ if(!ccptr->ctx || !ccptr->ccache)
515
+ rb_raise(cKrb5Exception, "credentials cache is closed or destroyed");
516
+
517
+ kerror = krb5_get_init_creds_opt_alloc(ptr->ctx, &opt);
518
+ if(kerror)
519
+ rb_raise(cKrb5Exception, "krb5_get_init_creds_opt_alloc: %s", error_message(kerror));
520
+
521
+ kerror = krb5_get_init_creds_opt_set_out_ccache(ptr->ctx, opt, ccptr->ccache);
522
+ if(kerror){
523
+ krb5_get_init_creds_opt_free(ptr->ctx, opt);
524
+ rb_raise(cKrb5Exception, "krb5_get_init_creds_opt_set_out_ccache: %s", error_message(kerror));
525
+ }
526
+ }
527
+
429
528
  kerror = krb5_get_init_creds_password(
430
529
  ptr->ctx,
431
530
  &ptr->creds,
@@ -435,9 +534,12 @@ static VALUE rkrb5_get_init_creds_passwd(int argc, VALUE* argv, VALUE self){
435
534
  NULL,
436
535
  0,
437
536
  service,
438
- NULL
537
+ opt
439
538
  );
440
539
 
540
+ if(opt)
541
+ krb5_get_init_creds_opt_free(ptr->ctx, opt);
542
+
441
543
  if(kerror)
442
544
  rb_raise(cKrb5Exception, "krb5_get_init_creds_password: %s", error_message(kerror));
443
545
 
@@ -446,7 +548,7 @@ static VALUE rkrb5_get_init_creds_passwd(int argc, VALUE* argv, VALUE self){
446
548
 
447
549
  /*
448
550
  * call-seq:
449
- * krb5.authenticate!(user, password, service = nil)
551
+ * krb5.authenticate!(principal:, password:, service: nil)
450
552
  *
451
553
  * Convenience method that: acquires initial credentials via password and
452
554
  * immediately verifies those credentials using `verify_init_creds` with
@@ -458,7 +560,9 @@ static VALUE rkrb5_get_init_creds_passwd(int argc, VALUE* argv, VALUE self){
458
560
  */
459
561
  static VALUE rkrb5_authenticate_bang(int argc, VALUE* argv, VALUE self){
460
562
  RUBY_KRB5* ptr;
461
- VALUE v_user, v_pass, v_service;
563
+ VALUE v_kwargs, v_user, v_pass, v_service;
564
+ ID kw_table[3] = { rb_intern("principal"), rb_intern("password"), rb_intern("service") };
565
+ VALUE kw_vals[3];
462
566
  char* user;
463
567
  char* pass;
464
568
  char* service;
@@ -479,8 +583,15 @@ static VALUE rkrb5_authenticate_bang(int argc, VALUE* argv, VALUE self){
479
583
  krb5_free_cred_contents(ptr->ctx, &ptr->creds);
480
584
  memset(&ptr->creds, 0, sizeof(ptr->creds));
481
585
 
482
- // Require user and password, optional service
483
- rb_scan_args(argc, argv, "21", &v_user, &v_pass, &v_service);
586
+ rb_scan_args(argc, argv, "0:", &v_kwargs);
587
+
588
+ if(NIL_P(v_kwargs))
589
+ v_kwargs = rb_hash_new();
590
+
591
+ rb_get_kwargs(v_kwargs, kw_table, 2, 1, kw_vals);
592
+ v_user = kw_vals[0];
593
+ v_pass = kw_vals[1];
594
+ v_service = kw_vals[2] == Qundef ? Qnil : kw_vals[2];
484
595
 
485
596
  Check_Type(v_user, T_STRING);
486
597
  Check_Type(v_pass, T_STRING);
@@ -516,14 +627,7 @@ static VALUE rkrb5_authenticate_bang(int argc, VALUE* argv, VALUE self){
516
627
  if(kerror)
517
628
  rb_raise(cKrb5Exception, "krb5_get_init_creds_password: %s", error_message(kerror));
518
629
 
519
- /*
520
- * Try strict verification first (AP-REQ nofail). If strict verification
521
- * cannot be performed (e.g. missing keytab or other environment issue),
522
- * fall back to the standard verification so authenticate! remains useful
523
- * in minimal test environments.
524
- */
525
630
  krb5_verify_init_creds_opt vicopt;
526
- krb5_error_code kerror_strict = 0;
527
631
 
528
632
  krb5_verify_init_creds_opt_init(&vicopt);
529
633
  krb5_verify_init_creds_opt_set_ap_req_nofail(&vicopt, TRUE);
@@ -536,19 +640,12 @@ static VALUE rkrb5_authenticate_bang(int argc, VALUE* argv, VALUE self){
536
640
  rb_raise(cKrb5Exception, "krb5_parse_name(service): %s", error_message(kerror));
537
641
  }
538
642
 
539
- // First, attempt strict verification
540
643
  kerror = krb5_verify_init_creds(ptr->ctx, &ptr->creds, server_princ, NULL, NULL, &vicopt);
541
644
 
542
645
  if(kerror){
543
- /* strict verification failed — try a best-effort standard verify */
544
- kerror_strict = kerror;
545
- kerror = krb5_verify_init_creds(ptr->ctx, &ptr->creds, server_princ, NULL, NULL, NULL);
546
- if(kerror){
547
- if(server_princ)
548
- krb5_free_principal(ptr->ctx, server_princ);
549
- /* raise the original strict-verification error to inform caller */
550
- rb_raise(cKrb5Exception, "krb5_verify_init_creds: %s", error_message(kerror_strict));
551
- }
646
+ if(server_princ)
647
+ krb5_free_principal(ptr->ctx, server_princ);
648
+ rb_raise(cKrb5Exception, "krb5_verify_init_creds: %s", error_message(kerror));
552
649
  }
553
650
 
554
651
  if(server_princ)
@@ -580,11 +677,14 @@ static VALUE rkrb5_close(VALUE self){
580
677
  if(ptr->princ)
581
678
  krb5_free_principal(ptr->ctx, ptr->princ);
582
679
 
583
- if(ptr->ctx)
680
+ if(ptr->ctx && ptr->rb_context == Qnil)
584
681
  krb5_free_context(ptr->ctx);
682
+ else if(ptr->rb_context != Qnil)
683
+ rkrb5_context_release(ptr->rb_context);
585
684
 
586
685
  ptr->ctx = NULL;
587
686
  ptr->princ = NULL;
687
+ ptr->rb_context = Qnil;
588
688
 
589
689
  return Qtrue;
590
690
  }
@@ -701,7 +801,7 @@ static VALUE rkrb5_get_permitted_enctypes(VALUE self){
701
801
 
702
802
  /*
703
803
  * call-seq:
704
- * krb5.verify_init_creds(server = nil, keytab = nil, ccache = nil)
804
+ * krb5.verify_init_creds(server: nil, keytab: nil, ccache: nil)
705
805
  *
706
806
  * Verifies the initial credentials currently stored in the internal
707
807
  * credentials structure. Optionally a server principal string, a
@@ -711,7 +811,10 @@ static VALUE rkrb5_get_permitted_enctypes(VALUE self){
711
811
  */
712
812
  static VALUE rkrb5_verify_init_creds(int argc, VALUE* argv, VALUE self){
713
813
  RUBY_KRB5* ptr;
714
- VALUE v_server, v_keytab, v_ccache;
814
+ VALUE v_server = Qnil, v_keytab = Qnil, v_ccache = Qnil;
815
+ VALUE v_kwargs = Qnil;
816
+ ID kw_table[3] = { rb_intern("server"), rb_intern("keytab"), rb_intern("ccache") };
817
+ VALUE kw_vals[3];
715
818
  krb5_error_code kerror;
716
819
  krb5_principal server_princ = NULL;
717
820
  RUBY_KRB5_KEYTAB* ktptr = NULL;
@@ -719,7 +822,15 @@ static VALUE rkrb5_verify_init_creds(int argc, VALUE* argv, VALUE self){
719
822
  krb5_keytab keytab = NULL;
720
823
  krb5_ccache *ccache_ptr = NULL;
721
824
 
722
- rb_scan_args(argc, argv, "03", &v_server, &v_keytab, &v_ccache);
825
+ rb_scan_args(argc, argv, "0:", &v_kwargs);
826
+
827
+ if(NIL_P(v_kwargs))
828
+ v_kwargs = rb_hash_new();
829
+
830
+ rb_get_kwargs(v_kwargs, kw_table, 0, 3, kw_vals);
831
+ if(kw_vals[0] != Qundef) v_server = kw_vals[0];
832
+ if(kw_vals[1] != Qundef) v_keytab = kw_vals[1];
833
+ if(kw_vals[2] != Qundef) v_ccache = kw_vals[2];
723
834
 
724
835
  TypedData_Get_Struct(self, RUBY_KRB5, &rkrb5_data_type, ptr);
725
836
 
@@ -734,12 +845,20 @@ static VALUE rkrb5_verify_init_creds(int argc, VALUE* argv, VALUE self){
734
845
  if(!NIL_P(v_keytab)){
735
846
  // Will raise TypeError if object isn't the expected Keytab typed data
736
847
  TypedData_Get_Struct(v_keytab, RUBY_KRB5_KEYTAB, &rkrb5_keytab_data_type, ktptr);
848
+
849
+ if(!ktptr->ctx || !ktptr->keytab)
850
+ rb_raise(cKrb5Exception, "keytab is closed");
851
+
737
852
  keytab = ktptr->keytab;
738
853
  }
739
854
 
740
855
  if(!NIL_P(v_ccache)){
741
856
  // Will raise TypeError if object isn't the expected CCache typed data
742
857
  TypedData_Get_Struct(v_ccache, RUBY_KRB5_CCACHE, &rkrb5_ccache_data_type, ccptr);
858
+
859
+ if(!ccptr->ctx || !ccptr->ccache)
860
+ rb_raise(cKrb5Exception, "credentials cache is closed or destroyed");
861
+
743
862
  ccache_ptr = &ccptr->ccache;
744
863
  }
745
864
 
@@ -747,6 +866,22 @@ static VALUE rkrb5_verify_init_creds(int argc, VALUE* argv, VALUE self){
747
866
  if(ptr->creds.client == NULL)
748
867
  rb_raise(cKrb5Exception, "no credentials have been acquired");
749
868
 
869
+ /* A resolved FILE cache may not exist on disk yet. Create it before
870
+ krb5_verify_init_creds attempts to store any credentials it fetches. */
871
+ if(ccache_ptr && *ccache_ptr){
872
+ krb5_principal cache_princ = NULL;
873
+
874
+ kerror = krb5_cc_get_principal(ptr->ctx, *ccache_ptr, &cache_princ);
875
+
876
+ if(kerror == KRB5_CC_NOTFOUND || kerror == KRB5_FCC_NOFILE)
877
+ kerror = krb5_cc_initialize(ptr->ctx, *ccache_ptr, ptr->creds.client);
878
+ else if(!kerror)
879
+ krb5_free_principal(ptr->ctx, cache_princ);
880
+
881
+ if(kerror)
882
+ rb_raise(cKrb5Exception, "credentials cache setup: %s", error_message(kerror));
883
+ }
884
+
750
885
  // Optional server principal (parse after context & arg validation)
751
886
  if(!NIL_P(v_server)){
752
887
  kerror = krb5_parse_name(ptr->ctx, StringValueCStr(v_server), &server_princ);
@@ -762,16 +897,11 @@ static VALUE rkrb5_verify_init_creds(int argc, VALUE* argv, VALUE self){
762
897
  if(kerror)
763
898
  rb_raise(cKrb5Exception, "krb5_verify_init_creds: %s", error_message(kerror));
764
899
 
765
- /* If the caller supplied a CredentialsCache object, store the verified
766
- credentials there so Ruby-level callers can inspect the cache. */
900
+ /* Include the initial credential in addition to any credentials fetched
901
+ and stored by krb5_verify_init_creds. */
767
902
  if(ccache_ptr && *ccache_ptr){
768
903
  krb5_error_code k2;
769
904
 
770
- k2 = krb5_cc_initialize(ptr->ctx, *ccache_ptr, ptr->creds.client);
771
-
772
- if(k2)
773
- rb_raise(cKrb5Exception, "krb5_cc_initialize: %s", error_message(k2));
774
-
775
905
  k2 = krb5_cc_store_cred(ptr->ctx, *ccache_ptr, &ptr->creds);
776
906
 
777
907
  if(k2)
@@ -781,6 +911,83 @@ static VALUE rkrb5_verify_init_creds(int argc, VALUE* argv, VALUE self){
781
911
  return Qtrue;
782
912
  }
783
913
 
914
+ /*
915
+ * call-seq:
916
+ * krb5.get_host_realm(hostname) -> Array
917
+ *
918
+ * Returns an array of realm names associated with +hostname+.
919
+ * Uses the Kerberos library's host-to-realm mapping (krb5.conf
920
+ * [domain_realm] section, DNS lookups, etc.).
921
+ */
922
+ static VALUE rkrb5_get_host_realm(VALUE self, VALUE v_host){
923
+ RUBY_KRB5* ptr;
924
+ char** realms;
925
+ char** p;
926
+ krb5_error_code kerror;
927
+
928
+ TypedData_Get_Struct(self, RUBY_KRB5, &rkrb5_data_type, ptr);
929
+
930
+ if(!ptr->ctx)
931
+ rb_raise(cKrb5Exception, "no context has been established");
932
+
933
+ Check_Type(v_host, T_STRING);
934
+
935
+ kerror = krb5_get_host_realm(ptr->ctx, StringValueCStr(v_host), &realms);
936
+
937
+ if(kerror)
938
+ rb_raise(cKrb5Exception, "krb5_get_host_realm: %s", error_message(kerror));
939
+
940
+ VALUE v_array = rb_ary_new();
941
+
942
+ for(p = realms; p && *p; p++)
943
+ rb_ary_push(v_array, rb_str_new2(*p));
944
+
945
+ krb5_free_host_realm(ptr->ctx, realms);
946
+
947
+ return v_array;
948
+ }
949
+
950
+ /*
951
+ * call-seq:
952
+ * krb5.expand_hostname(hostname) -> String
953
+ *
954
+ * Canonicalizes +hostname+ by performing a forward DNS lookup followed
955
+ * by a reverse lookup, returning the canonical hostname.
956
+ */
957
+ static VALUE rkrb5_expand_hostname(VALUE self, VALUE v_host){
958
+ RUBY_KRB5* ptr;
959
+ char* canonical;
960
+ krb5_error_code kerror;
961
+
962
+ TypedData_Get_Struct(self, RUBY_KRB5, &rkrb5_data_type, ptr);
963
+
964
+ if(!ptr->ctx)
965
+ rb_raise(cKrb5Exception, "no context has been established");
966
+
967
+ Check_Type(v_host, T_STRING);
968
+
969
+ kerror = krb5_expand_hostname(ptr->ctx, StringValueCStr(v_host), &canonical);
970
+
971
+ if(kerror)
972
+ rb_raise(cKrb5Exception, "krb5_expand_hostname: %s", error_message(kerror));
973
+
974
+ VALUE v_result = rb_str_new2(canonical);
975
+ krb5_free_string(ptr->ctx, canonical);
976
+
977
+ return v_result;
978
+ }
979
+
980
+ /*
981
+ * call-seq:
982
+ * Kerberos::Krb5.thread_safe?
983
+ *
984
+ * Returns true if the Kerberos library was built with multithread support,
985
+ * false otherwise.
986
+ */
987
+ static VALUE rkrb5_s_thread_safe(VALUE klass){
988
+ return krb5_is_thread_safe() ? Qtrue : Qfalse;
989
+ }
990
+
784
991
  void Init_rkerberos(void){
785
992
  mKerberos = rb_define_module("Kerberos");
786
993
  cKrb5 = rb_define_class_under(mKerberos, "Krb5", rb_cObject);
@@ -790,7 +997,10 @@ void Init_rkerberos(void){
790
997
  rb_define_alloc_func(cKrb5, rkrb5_allocate);
791
998
 
792
999
  // Initializers
793
- rb_define_method(cKrb5, "initialize", rkrb5_initialize, 0);
1000
+ rb_define_method(cKrb5, "initialize", rkrb5_initialize, -1);
1001
+
1002
+ // Singleton Methods
1003
+ rb_define_singleton_method(cKrb5, "thread_safe?", rkrb5_s_thread_safe, 0);
794
1004
 
795
1005
  // Krb5 Methods
796
1006
  rb_define_method(cKrb5, "authenticate!", rkrb5_authenticate_bang, -1);
@@ -803,13 +1013,20 @@ void Init_rkerberos(void){
803
1013
  rb_define_method(cKrb5, "get_permitted_enctypes", rkrb5_get_permitted_enctypes, 0);
804
1014
  rb_define_method(cKrb5, "set_default_realm", rkrb5_set_default_realm, -1);
805
1015
  rb_define_method(cKrb5, "verify_init_creds", rkrb5_verify_init_creds, -1);
1016
+ rb_define_method(cKrb5, "get_host_realm", rkrb5_get_host_realm, 1);
1017
+ rb_define_method(cKrb5, "expand_hostname", rkrb5_expand_hostname, 1);
806
1018
 
807
1019
  // Aliases
808
1020
  rb_define_alias(cKrb5, "default_realm", "get_default_realm");
1021
+ rb_define_alias(cKrb5, "default_realm=", "set_default_realm");
809
1022
  rb_define_alias(cKrb5, "default_principal", "get_default_principal");
1023
+ rb_define_alias(cKrb5, "host_realm", "get_host_realm");
1024
+ rb_define_alias(cKrb5, "init_creds_password", "get_init_creds_password");
1025
+ rb_define_alias(cKrb5, "init_creds_keytab", "get_init_creds_keytab");
1026
+ rb_define_alias(cKrb5, "permitted_enctypes", "get_permitted_enctypes");
810
1027
 
811
- /* 0.2.3: The version of the custom rkerberos library */
812
- rb_define_const(cKrb5, "VERSION", rb_str_new2("0.2.3"));
1028
+ /* 0.3.1: The version of the custom rkerberos library */
1029
+ rb_define_const(cKrb5, "VERSION", rb_str_new2("0.3.1"));
813
1030
 
814
1031
  // Encoding type constants
815
1032
 
@@ -8,6 +8,8 @@
8
8
 
9
9
  // Make the context data type visible to other C files
10
10
  extern const rb_data_type_t rkrb5_context_data_type;
11
+ // Make the Krb5 data type visible to other C files
12
+ extern const rb_data_type_t rkrb5_data_type;
11
13
  // Make the config data type visible to other C files
12
14
  extern const rb_data_type_t rkadm5_config_data_type;
13
15
 
@@ -47,6 +49,10 @@ void Init_ccache();
47
49
  // Defined in rkerberos.c
48
50
  VALUE rb_hash_aref2(VALUE, VALUE);
49
51
 
52
+ // Defined in context.c
53
+ krb5_context rkrb5_context_borrow(VALUE);
54
+ void rkrb5_context_release(VALUE);
55
+
50
56
  // Variable declarations
51
57
  extern VALUE mKerberos;
52
58
  extern VALUE cKrb5;
@@ -67,12 +73,15 @@ typedef struct {
67
73
  krb5_creds creds;
68
74
  krb5_principal princ;
69
75
  krb5_keytab keytab;
76
+ VALUE rb_context;
70
77
  } RUBY_KRB5;
71
78
 
72
79
  // Kerberos::Context
73
80
  typedef struct {
74
81
  krb5_context ctx;
75
82
  krb5_enctype etypes;
83
+ size_t borrowers;
84
+ int closed;
76
85
  } RUBY_KRB5_CONTEXT;
77
86
 
78
87
  // Kerberos::Kadm5
@@ -81,6 +90,7 @@ typedef struct {
81
90
  krb5_principal princ;
82
91
  void* handle;
83
92
  char** db_args;
93
+ VALUE rb_context; // Ruby Context object when borrowed, Qnil if ctx is owned
84
94
  } RUBY_KADM5;
85
95
 
86
96
  // Kerberos::Krb5::Keytab::Entry
@@ -96,23 +106,27 @@ typedef struct {
96
106
  krb5_context ctx;
97
107
  krb5_creds creds;
98
108
  krb5_keytab keytab;
109
+ VALUE rb_context; // Ruby Context object when borrowed, Qnil if ctx is owned
99
110
  } RUBY_KRB5_KEYTAB;
100
111
 
101
112
  typedef struct {
102
113
  krb5_context ctx;
103
114
  krb5_principal principal;
115
+ VALUE rb_context; // Ruby Context object when borrowed, Qnil if ctx is owned
104
116
  } RUBY_KRB5_PRINC;
105
117
 
106
118
  typedef struct {
107
119
  krb5_context ctx;
108
120
  krb5_ccache ccache;
109
121
  krb5_principal principal;
122
+ VALUE rb_context; // Ruby Context object when borrowed, Qnil if ctx is owned
110
123
  } RUBY_KRB5_CCACHE;
111
124
 
112
125
  #ifdef HAVE_KADM5_ADMIN_H
113
126
  typedef struct {
114
127
  krb5_context ctx;
115
128
  kadm5_config_params config;
129
+ VALUE rb_context; // Ruby Context object when borrowed, Qnil if ctx is owned
116
130
  } RUBY_KADM5_CONFIG;
117
131
 
118
132
  typedef struct {
data/rkerberos.gemspec CHANGED
@@ -2,15 +2,18 @@ require 'rubygems'
2
2
 
3
3
  Gem::Specification.new do |spec|
4
4
  spec.name = 'rkerberos'
5
- spec.version = '0.2.3'
5
+ spec.version = '0.3.1'
6
6
  spec.authors = ['Daniel Berger', 'Dominic Cleal', 'Simon Levermann']
7
7
  spec.license = 'Artistic-2.0'
8
8
  spec.email = ['djberg96@gmail.com', 'dominic@cleal.org', 'simon-rubygems@slevermann.de']
9
- spec.homepage = 'http://github.com/rkerberos/rkerberos'
10
- spec.summary = 'A Ruby interface for the the Kerberos library'
9
+ spec.homepage = 'https://github.com/rkerberos/rkerberos'
10
+ spec.summary = 'A Ruby interface for the Kerberos library'
11
+ spec.required_ruby_version = '>= 3.2'
11
12
  spec.test_files = Dir['spec/**/*_spec.rb']
12
13
  spec.extensions = ['ext/rkerberos/extconf.rb']
13
- spec.files = Dir['**/*'].grep_v(%r{\A(?:\.git|docker|Dockerfile)})
14
+ spec.files = Dir['**/*']
15
+ .select { |file| File.file?(file) }
16
+ .grep_v(%r{\A(?:\.git|docker|Dockerfile|Gemfile\.lock\z|tmp(?:/|\z))|\.gem\z})
14
17
 
15
18
  spec.add_development_dependency('rake-compiler')
16
19
  spec.add_development_dependency('rspec', '>= 3.0')
@@ -22,13 +25,13 @@ Gem::Specification.new do |spec|
22
25
  EOF
23
26
 
24
27
  spec.metadata = {
25
- 'homepage_uri' => 'https://github.com/rkerberos/rkerberos',
26
28
  'bug_tracker_uri' => 'https://github.com/rkerberos/rkerberos/issues',
27
- 'changelog_uri' => 'https://github.com/rkerberos/rkerberos/blob/main/CHANGES.md',
29
+ 'changelog_uri' => 'https://github.com/rkerberos/rkerberos/blob/master/CHANGES.md',
28
30
  'documentation_uri' => 'https://github.com/rkerberos/rkerberos/wiki',
31
+ 'homepage_uri' => 'https://github.com/rkerberos/rkerberos',
29
32
  'source_code_uri' => 'https://github.com/rkerberos/rkerberos',
30
33
  'wiki_uri' => 'https://github.com/rkerberos/rkerberos/wiki',
31
- 'github_repo' => 'https://github.com/djberg96/rkerberos',
34
+ 'github_repo' => 'https://github.com/rkerberos/rkerberos',
32
35
  'funding_uri' => 'https://github.com/sponsors/rkerberos',
33
36
  'rubygems_mfa_required' => 'true'
34
37
  }