rkerberos 0.3.0 → 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.
@@ -20,6 +20,8 @@ void rkrb5_keytab_typed_free(void *ptr) {
20
20
  krb5_free_cred_contents(kt->ctx, &kt->creds);
21
21
  if (kt->ctx && kt->rb_context == Qnil)
22
22
  krb5_free_context(kt->ctx);
23
+ else if (kt->rb_context != Qnil)
24
+ rkrb5_context_release(kt->rb_context);
23
25
  free(kt);
24
26
  }
25
27
 
@@ -54,7 +56,7 @@ typedef struct {
54
56
  static VALUE rkrb5_keytab_each_body(VALUE arg){
55
57
  keytab_each_arg* ea = (keytab_each_arg*)arg;
56
58
  krb5_keytab_entry entry;
57
- krb5_error_code kerror;
59
+ krb5_error_code kerror, iteration_error;
58
60
  char* principal;
59
61
  VALUE v_kt_entry;
60
62
 
@@ -79,9 +81,13 @@ static VALUE rkrb5_keytab_each_body(VALUE arg){
79
81
  rb_yield(v_kt_entry);
80
82
  }
81
83
 
82
- ea->cursor_active = 0;
84
+ iteration_error = kerror;
83
85
 
84
86
  kerror = krb5_kt_end_seq_get(ea->ctx, ea->keytab, &ea->cursor);
87
+ ea->cursor_active = 0;
88
+
89
+ if(iteration_error != KRB5_KT_END)
90
+ rb_raise(cKrb5Exception, "krb5_kt_next_entry: %s", error_message(iteration_error));
85
91
 
86
92
  if(kerror)
87
93
  rb_raise(cKrb5Exception, "krb5_kt_end_seq_get: %s", error_message(kerror));
@@ -114,6 +120,9 @@ static VALUE rkrb5_keytab_each(VALUE self){
114
120
 
115
121
  TypedData_Get_Struct(self, RUBY_KRB5_KEYTAB, &rkrb5_keytab_data_type, ptr);
116
122
 
123
+ if(!ptr->ctx || !ptr->keytab)
124
+ rb_raise(cKrb5Exception, "keytab is closed");
125
+
117
126
  ea.ctx = ptr->ctx;
118
127
  ea.keytab = ptr->keytab;
119
128
 
@@ -144,6 +153,9 @@ static VALUE rkrb5_keytab_default_name(VALUE self){
144
153
 
145
154
  TypedData_Get_Struct(self, RUBY_KRB5_KEYTAB, &rkrb5_keytab_data_type, ptr);
146
155
 
156
+ if(!ptr->ctx)
157
+ rb_raise(cKrb5Exception, "no context has been established");
158
+
147
159
  kerror = krb5_kt_default_name(ptr->ctx, default_name, MAX_KEYTAB_NAME_LEN);
148
160
 
149
161
  if(kerror)
@@ -177,6 +189,8 @@ static VALUE rkrb5_keytab_close(VALUE self){
177
189
 
178
190
  if(ptr->ctx && ptr->rb_context == Qnil)
179
191
  krb5_free_context(ptr->ctx);
192
+ else if(ptr->rb_context != Qnil)
193
+ rkrb5_context_release(ptr->rb_context);
180
194
 
181
195
  ptr->ctx = NULL;
182
196
  ptr->rb_context = Qnil;
@@ -233,13 +247,14 @@ static VALUE rkrb5_keytab_add_entry(int argc, VALUE* argv, VALUE self){
233
247
 
234
248
  memset(&entry, 0, sizeof(entry));
235
249
 
250
+ entry.vno = NIL_P(v_vno) ? 1 : NUM2INT(v_vno);
251
+ entry.key.enctype = NIL_P(v_enctype) ? ENCTYPE_AES256_CTS_HMAC_SHA1_96 : NUM2INT(v_enctype);
252
+
236
253
  kerror = krb5_parse_name(ptr->ctx, StringValueCStr(v_principal), &entry.principal);
237
254
 
238
255
  if(kerror)
239
256
  rb_raise(cKrb5Exception, "krb5_parse_name: %s", error_message(kerror));
240
257
 
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
258
  entry.timestamp = time(NULL);
244
259
 
245
260
  // Derive the salt from the principal
@@ -323,14 +338,14 @@ static VALUE rkrb5_keytab_remove_entry(int argc, VALUE* argv, VALUE self){
323
338
 
324
339
  Check_Type(v_principal, T_STRING);
325
340
 
341
+ match_vno = NIL_P(v_vno) ? 0 : NUM2INT(v_vno);
342
+ match_enctype = NIL_P(v_enctype) ? 0 : NUM2INT(v_enctype);
343
+
326
344
  kerror = krb5_parse_name(ptr->ctx, StringValueCStr(v_principal), &match_princ);
327
345
 
328
346
  if(kerror)
329
347
  rb_raise(cKrb5Exception, "krb5_parse_name: %s", error_message(kerror));
330
348
 
331
- match_vno = NIL_P(v_vno) ? 0 : NUM2INT(v_vno);
332
- match_enctype = NIL_P(v_enctype) ? 0 : NUM2INT(v_enctype);
333
-
334
349
  // Retrieve the full entry via krb5_kt_get_entry and then pass the
335
350
  // complete struct to krb5_kt_remove_entry so all fields match exactly.
336
351
  // Loop to remove every matching entry when vno/enctype are wildcards.
@@ -383,26 +398,22 @@ static VALUE rkrb5_keytab_get_entry(int argc, VALUE* argv, VALUE self){
383
398
 
384
399
  TypedData_Get_Struct(self, RUBY_KRB5_KEYTAB, &rkrb5_keytab_data_type, ptr);
385
400
 
401
+ if(!ptr->ctx || !ptr->keytab)
402
+ rb_raise(cKrb5Exception, "keytab is closed");
403
+
386
404
  rb_scan_args(argc, argv, "12", &v_principal, &v_vno, &v_enctype);
387
405
 
388
406
  Check_Type(v_principal, T_STRING);
389
407
  name = StringValueCStr(v_principal);
390
408
 
409
+ vno = NIL_P(v_vno) ? 0 : NUM2INT(v_vno);
410
+ enctype = NIL_P(v_enctype) ? 0 : NUM2INT(v_enctype);
411
+
391
412
  kerror = krb5_parse_name(ptr->ctx, name, &principal);
392
413
 
393
414
  if(kerror)
394
415
  rb_raise(cKrb5Exception, "krb5_parse_name: %s", error_message(kerror));
395
416
 
396
- if(NIL_P(v_vno))
397
- vno = 0;
398
- else
399
- vno = NUM2INT(v_vno);
400
-
401
- if(NIL_P(v_enctype))
402
- enctype = 0;
403
- else
404
- enctype = NUM2INT(v_enctype);
405
-
406
417
  kerror = krb5_kt_get_entry(
407
418
  ptr->ctx,
408
419
  ptr->keytab,
@@ -566,17 +577,7 @@ static VALUE rkrb5_keytab_initialize(int argc, VALUE* argv, VALUE self){
566
577
 
567
578
  // Initialize or borrow the context
568
579
  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->ctx = rkrb5_context_borrow(v_context);
580
581
  ptr->rb_context = v_context;
581
582
  }
582
583
  else{
@@ -629,7 +630,7 @@ typedef struct {
629
630
  static VALUE rkrb5_s_keytab_foreach_body(VALUE arg){
630
631
  keytab_foreach_arg* fa = (keytab_foreach_arg*)arg;
631
632
  krb5_keytab_entry entry;
632
- krb5_error_code kerror;
633
+ krb5_error_code kerror, iteration_error;
633
634
  char* principal;
634
635
  VALUE v_kt_entry;
635
636
 
@@ -654,9 +655,13 @@ static VALUE rkrb5_s_keytab_foreach_body(VALUE arg){
654
655
  rb_yield(v_kt_entry);
655
656
  }
656
657
 
657
- fa->cursor_active = 0;
658
+ iteration_error = kerror;
658
659
 
659
660
  kerror = krb5_kt_end_seq_get(fa->ctx, fa->keytab, &fa->cursor);
661
+ fa->cursor_active = 0;
662
+
663
+ if(iteration_error != KRB5_KT_END)
664
+ rb_raise(cKrb5Exception, "krb5_kt_next_entry: %s", error_message(iteration_error));
660
665
 
661
666
  if(kerror)
662
667
  rb_raise(cKrb5Exception, "krb5_kt_end_seq_get: %s", error_message(kerror));
@@ -18,6 +18,8 @@ static void rkrb5_princ_typed_free(void *ptr) {
18
18
  krb5_free_principal(p->ctx, p->principal);
19
19
  if (p->ctx && p->rb_context == Qnil)
20
20
  krb5_free_context(p->ctx);
21
+ else if (p->rb_context != Qnil)
22
+ rkrb5_context_release(p->rb_context);
21
23
  free(p);
22
24
  }
23
25
 
@@ -86,17 +88,7 @@ static VALUE rkrb5_princ_initialize(int argc, VALUE* argv, VALUE self){
86
88
 
87
89
  // Initialize or borrow the context
88
90
  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);
95
-
96
- if(!ctx_ptr->ctx)
97
- rb_raise(cKrb5Exception, "context is closed");
98
-
99
- ptr->ctx = ctx_ptr->ctx;
91
+ ptr->ctx = rkrb5_context_borrow(v_context);
100
92
  ptr->rb_context = v_context;
101
93
  }
102
94
  else{
@@ -145,6 +137,36 @@ static VALUE rkrb5_princ_initialize(int argc, VALUE* argv, VALUE self){
145
137
  return self;
146
138
  }
147
139
 
140
+ /*
141
+ * call-seq:
142
+ * principal.close
143
+ *
144
+ * Releases the principal and its Kerberos context lease. Once closed, the
145
+ * principal cannot be reused.
146
+ */
147
+ static VALUE rkrb5_princ_close(VALUE self){
148
+ RUBY_KRB5_PRINC* ptr;
149
+
150
+ TypedData_Get_Struct(self, RUBY_KRB5_PRINC, &rkrb5_princ_data_type, ptr);
151
+
152
+ if(!ptr->ctx)
153
+ return self;
154
+
155
+ if(ptr->principal)
156
+ krb5_free_principal(ptr->ctx, ptr->principal);
157
+
158
+ if(ptr->rb_context == Qnil)
159
+ krb5_free_context(ptr->ctx);
160
+ else
161
+ rkrb5_context_release(ptr->rb_context);
162
+
163
+ ptr->ctx = NULL;
164
+ ptr->principal = NULL;
165
+ ptr->rb_context = Qnil;
166
+
167
+ return self;
168
+ }
169
+
148
170
  /*
149
171
  * call-seq:
150
172
  * principal.realm
@@ -153,13 +175,16 @@ static VALUE rkrb5_princ_initialize(int argc, VALUE* argv, VALUE self){
153
175
  */
154
176
  static VALUE rkrb5_princ_get_realm(VALUE self){
155
177
  RUBY_KRB5_PRINC* ptr;
178
+ krb5_data* realm;
156
179
 
157
180
  TypedData_Get_Struct(self, RUBY_KRB5_PRINC, &rkrb5_princ_data_type, ptr);
158
181
 
159
182
  if(!ptr->principal)
160
183
  rb_raise(cKrb5Exception, "no principal has been established");
161
184
 
162
- return rb_str_new2(krb5_princ_realm(ptr->ctx, ptr->principal)->data);
185
+ realm = krb5_princ_realm(ptr->ctx, ptr->principal);
186
+
187
+ return rb_str_new(realm->data, realm->length);
163
188
  }
164
189
 
165
190
  /*
@@ -195,6 +220,10 @@ static VALUE rkrb5_princ_equal(VALUE self, VALUE v_other){
195
220
  VALUE v_bool = Qfalse;
196
221
 
197
222
  TypedData_Get_Struct(self, RUBY_KRB5_PRINC, &rkrb5_princ_data_type, ptr1);
223
+
224
+ if(!rb_obj_is_kind_of(v_other, cKrb5Principal))
225
+ return Qfalse;
226
+
198
227
  TypedData_Get_Struct(v_other, RUBY_KRB5_PRINC, &rkrb5_princ_data_type, ptr2);
199
228
 
200
229
  if(!ptr1->principal || !ptr2->principal)
@@ -349,6 +378,7 @@ void Init_principal(void){
349
378
  rb_define_method(cKrb5Principal, "==", rkrb5_princ_equal, 1);
350
379
  rb_define_method(cKrb5Principal, "principal_type", rkrb5_princ_get_type, 0);
351
380
  rb_define_method(cKrb5Principal, "components", rkrb5_princ_components, 0);
381
+ rb_define_method(cKrb5Principal, "close", rkrb5_princ_close, 0);
352
382
 
353
383
  // Attributes
354
384
 
@@ -43,6 +43,8 @@ static void rkrb5_typed_free(void *ptr) {
43
43
  krb5_free_principal(r->ctx, r->princ);
44
44
  if (r->ctx && r->rb_context == Qnil)
45
45
  krb5_free_context(r->ctx);
46
+ else if (r->rb_context != Qnil)
47
+ rkrb5_context_release(r->rb_context);
46
48
  free(r);
47
49
  }
48
50
 
@@ -74,8 +76,8 @@ static VALUE rkrb5_allocate(VALUE klass){
74
76
  * If a +context+ keyword argument is provided, it must be a
75
77
  * Kerberos::Krb5::Context object. The context will be borrowed rather than
76
78
  * creating a new one internally via krb5_init_context. The caller is
77
- * responsible for keeping the Context object alive for the lifetime of
78
- * this Krb5 instance.
79
+ * retained for the lifetime of this Krb5 instance. The Context cannot be
80
+ * closed normally until this object has been closed.
79
81
  *
80
82
  * A block form is also supported. If a block is given, the object is
81
83
  * yielded to the block and automatically closed when the block returns.
@@ -98,17 +100,7 @@ static VALUE rkrb5_initialize(int argc, VALUE* argv, VALUE self){
98
100
  v_context = kw_vals[0] == Qundef ? Qnil : kw_vals[0];
99
101
 
100
102
  if(!NIL_P(v_context)){
101
- RUBY_KRB5_CONTEXT* ctx_ptr;
102
-
103
- if(!rb_obj_is_kind_of(v_context, cKrb5Context))
104
- rb_raise(rb_eTypeError, "context must be a Kerberos::Krb5::Context object");
105
-
106
- TypedData_Get_Struct(v_context, RUBY_KRB5_CONTEXT, &rkrb5_context_data_type, ctx_ptr);
107
-
108
- if(!ctx_ptr->ctx)
109
- rb_raise(cKrb5Exception, "context is closed");
110
-
111
- ptr->ctx = ctx_ptr->ctx;
103
+ ptr->ctx = rkrb5_context_borrow(v_context);
112
104
  ptr->rb_context = v_context;
113
105
  }
114
106
  else{
@@ -139,6 +131,9 @@ static VALUE rkrb5_get_default_realm(VALUE self){
139
131
 
140
132
  TypedData_Get_Struct(self, RUBY_KRB5, &rkrb5_data_type, ptr);
141
133
 
134
+ if(!ptr->ctx)
135
+ rb_raise(cKrb5Exception, "no context has been established");
136
+
142
137
  kerror = krb5_get_default_realm(ptr->ctx, &realm);
143
138
 
144
139
  if(kerror)
@@ -165,6 +160,9 @@ static VALUE rkrb5_set_default_realm(int argc, VALUE* argv, VALUE self){
165
160
 
166
161
  TypedData_Get_Struct(self, RUBY_KRB5, &rkrb5_data_type, ptr);
167
162
 
163
+ if(!ptr->ctx)
164
+ rb_raise(cKrb5Exception, "no context has been established");
165
+
168
166
  rb_scan_args(argc, argv, "01", &v_realm);
169
167
 
170
168
  if(NIL_P(v_realm)){
@@ -317,6 +315,11 @@ static VALUE rkrb5_get_init_creds_keytab(int argc, VALUE* argv, VALUE self){
317
315
  RUBY_KRB5_CCACHE* ccptr;
318
316
  TypedData_Get_Struct(v_ccache, RUBY_KRB5_CCACHE, &rkrb5_ccache_data_type, ccptr);
319
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
+
320
323
  kerror = krb5_get_init_creds_opt_set_out_ccache(ptr->ctx, opt, ccptr->ccache);
321
324
  if(kerror) {
322
325
  krb5_get_init_creds_opt_free(ptr->ctx, opt);
@@ -384,6 +387,9 @@ static VALUE rkrb5_change_password(VALUE self, VALUE v_old, VALUE v_new){
384
387
  if(!ptr->princ)
385
388
  rb_raise(cKrb5Exception, "no principal has been established");
386
389
 
390
+ memset(&result_string, 0, sizeof(result_string));
391
+ memset(&pw_result_string, 0, sizeof(pw_result_string));
392
+
387
393
  krb5_free_cred_contents(ptr->ctx, &ptr->creds);
388
394
  memset(&ptr->creds, 0, sizeof(ptr->creds));
389
395
 
@@ -505,6 +511,9 @@ static VALUE rkrb5_get_init_creds_passwd(int argc, VALUE* argv, VALUE self){
505
511
  RUBY_KRB5_CCACHE* ccptr;
506
512
  TypedData_Get_Struct(v_ccache, RUBY_KRB5_CCACHE, &rkrb5_ccache_data_type, ccptr);
507
513
 
514
+ if(!ccptr->ctx || !ccptr->ccache)
515
+ rb_raise(cKrb5Exception, "credentials cache is closed or destroyed");
516
+
508
517
  kerror = krb5_get_init_creds_opt_alloc(ptr->ctx, &opt);
509
518
  if(kerror)
510
519
  rb_raise(cKrb5Exception, "krb5_get_init_creds_opt_alloc: %s", error_message(kerror));
@@ -618,14 +627,7 @@ static VALUE rkrb5_authenticate_bang(int argc, VALUE* argv, VALUE self){
618
627
  if(kerror)
619
628
  rb_raise(cKrb5Exception, "krb5_get_init_creds_password: %s", error_message(kerror));
620
629
 
621
- /*
622
- * Try strict verification first (AP-REQ nofail). If strict verification
623
- * cannot be performed (e.g. missing keytab or other environment issue),
624
- * fall back to the standard verification so authenticate! remains useful
625
- * in minimal test environments.
626
- */
627
630
  krb5_verify_init_creds_opt vicopt;
628
- krb5_error_code kerror_strict = 0;
629
631
 
630
632
  krb5_verify_init_creds_opt_init(&vicopt);
631
633
  krb5_verify_init_creds_opt_set_ap_req_nofail(&vicopt, TRUE);
@@ -638,19 +640,12 @@ static VALUE rkrb5_authenticate_bang(int argc, VALUE* argv, VALUE self){
638
640
  rb_raise(cKrb5Exception, "krb5_parse_name(service): %s", error_message(kerror));
639
641
  }
640
642
 
641
- // First, attempt strict verification
642
643
  kerror = krb5_verify_init_creds(ptr->ctx, &ptr->creds, server_princ, NULL, NULL, &vicopt);
643
644
 
644
645
  if(kerror){
645
- /* strict verification failed — try a best-effort standard verify */
646
- kerror_strict = kerror;
647
- kerror = krb5_verify_init_creds(ptr->ctx, &ptr->creds, server_princ, NULL, NULL, NULL);
648
- if(kerror){
649
- if(server_princ)
650
- krb5_free_principal(ptr->ctx, server_princ);
651
- /* raise the original strict-verification error to inform caller */
652
- rb_raise(cKrb5Exception, "krb5_verify_init_creds: %s", error_message(kerror_strict));
653
- }
646
+ if(server_princ)
647
+ krb5_free_principal(ptr->ctx, server_princ);
648
+ rb_raise(cKrb5Exception, "krb5_verify_init_creds: %s", error_message(kerror));
654
649
  }
655
650
 
656
651
  if(server_princ)
@@ -684,6 +679,8 @@ static VALUE rkrb5_close(VALUE self){
684
679
 
685
680
  if(ptr->ctx && ptr->rb_context == Qnil)
686
681
  krb5_free_context(ptr->ctx);
682
+ else if(ptr->rb_context != Qnil)
683
+ rkrb5_context_release(ptr->rb_context);
687
684
 
688
685
  ptr->ctx = NULL;
689
686
  ptr->princ = NULL;
@@ -848,12 +845,20 @@ static VALUE rkrb5_verify_init_creds(int argc, VALUE* argv, VALUE self){
848
845
  if(!NIL_P(v_keytab)){
849
846
  // Will raise TypeError if object isn't the expected Keytab typed data
850
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
+
851
852
  keytab = ktptr->keytab;
852
853
  }
853
854
 
854
855
  if(!NIL_P(v_ccache)){
855
856
  // Will raise TypeError if object isn't the expected CCache typed data
856
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
+
857
862
  ccache_ptr = &ccptr->ccache;
858
863
  }
859
864
 
@@ -861,6 +866,22 @@ static VALUE rkrb5_verify_init_creds(int argc, VALUE* argv, VALUE self){
861
866
  if(ptr->creds.client == NULL)
862
867
  rb_raise(cKrb5Exception, "no credentials have been acquired");
863
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
+
864
885
  // Optional server principal (parse after context & arg validation)
865
886
  if(!NIL_P(v_server)){
866
887
  kerror = krb5_parse_name(ptr->ctx, StringValueCStr(v_server), &server_princ);
@@ -876,16 +897,11 @@ static VALUE rkrb5_verify_init_creds(int argc, VALUE* argv, VALUE self){
876
897
  if(kerror)
877
898
  rb_raise(cKrb5Exception, "krb5_verify_init_creds: %s", error_message(kerror));
878
899
 
879
- /* If the caller supplied a CredentialsCache object, store the verified
880
- 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. */
881
902
  if(ccache_ptr && *ccache_ptr){
882
903
  krb5_error_code k2;
883
904
 
884
- k2 = krb5_cc_initialize(ptr->ctx, *ccache_ptr, ptr->creds.client);
885
-
886
- if(k2)
887
- rb_raise(cKrb5Exception, "krb5_cc_initialize: %s", error_message(k2));
888
-
889
905
  k2 = krb5_cc_store_cred(ptr->ctx, *ccache_ptr, &ptr->creds);
890
906
 
891
907
  if(k2)
@@ -1009,8 +1025,8 @@ void Init_rkerberos(void){
1009
1025
  rb_define_alias(cKrb5, "init_creds_keytab", "get_init_creds_keytab");
1010
1026
  rb_define_alias(cKrb5, "permitted_enctypes", "get_permitted_enctypes");
1011
1027
 
1012
- /* 0.3.0: The version of the custom rkerberos library */
1013
- rb_define_const(cKrb5, "VERSION", rb_str_new2("0.3.0"));
1028
+ /* 0.3.1: The version of the custom rkerberos library */
1029
+ rb_define_const(cKrb5, "VERSION", rb_str_new2("0.3.1"));
1014
1030
 
1015
1031
  // Encoding type constants
1016
1032
 
@@ -49,6 +49,10 @@ void Init_ccache();
49
49
  // Defined in rkerberos.c
50
50
  VALUE rb_hash_aref2(VALUE, VALUE);
51
51
 
52
+ // Defined in context.c
53
+ krb5_context rkrb5_context_borrow(VALUE);
54
+ void rkrb5_context_release(VALUE);
55
+
52
56
  // Variable declarations
53
57
  extern VALUE mKerberos;
54
58
  extern VALUE cKrb5;
@@ -76,6 +80,8 @@ typedef struct {
76
80
  typedef struct {
77
81
  krb5_context ctx;
78
82
  krb5_enctype etypes;
83
+ size_t borrowers;
84
+ int closed;
79
85
  } RUBY_KRB5_CONTEXT;
80
86
 
81
87
  // Kerberos::Kadm5
data/rkerberos.gemspec CHANGED
@@ -2,7 +2,7 @@ require 'rubygems'
2
2
 
3
3
  Gem::Specification.new do |spec|
4
4
  spec.name = 'rkerberos'
5
- spec.version = '0.3.0'
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']
data/spec/config_spec.rb CHANGED
@@ -28,6 +28,14 @@ RSpec.describe 'Kerberos::Kadm5::Config', :kadm5 do
28
28
  expect(c2.realm).to eq(c1.realm)
29
29
  end
30
30
 
31
+ it 'releases its context lease after copying the configuration' do
32
+ ctx = Kerberos::Krb5::Context.new
33
+ config = klass.new(context: ctx)
34
+
35
+ expect { ctx.close }.not_to raise_error
36
+ expect(config.realm).to be_a(String)
37
+ end
38
+
31
39
  it 'raises TypeError for non-Context context argument' do
32
40
  expect { klass.new(context: "bad") }.to raise_error(TypeError)
33
41
  end
data/spec/context_spec.rb CHANGED
@@ -16,6 +16,51 @@ RSpec.describe Kerberos::Krb5::Context do
16
16
  it 'can be called multiple times without error' do
17
17
  expect { 3.times { context.close } }.not_to raise_error
18
18
  end
19
+
20
+ it 'raises while a dependent wrapper remains open' do
21
+ krb5 = Kerberos::Krb5.new(context: context)
22
+
23
+ expect { context.close }
24
+ .to raise_error(Kerberos::Krb5::Exception, /1 dependent wrapper/)
25
+ expect(context.default_realm).to be_a(String)
26
+
27
+ krb5.close
28
+ expect { context.close }.not_to raise_error
29
+ end
30
+
31
+ it 'tracks multiple dependent wrappers independently' do
32
+ first = Kerberos::Krb5.new(context: context)
33
+ second = Kerberos::Krb5::Principal.new(name: 'user', context: context)
34
+
35
+ expect { context.close }
36
+ .to raise_error(Kerberos::Krb5::Exception, /2 dependent wrappers/)
37
+
38
+ first.close
39
+ expect { context.close }
40
+ .to raise_error(Kerberos::Krb5::Exception, /1 dependent wrapper/)
41
+
42
+ second.close
43
+ expect { context.close }.not_to raise_error
44
+ end
45
+
46
+ it 'supports a forced logical close with deferred native cleanup' do
47
+ krb5 = Kerberos::Krb5.new(context: context)
48
+ realm = krb5.default_realm
49
+
50
+ expect(context.close(force: true)).to equal(context)
51
+ expect { context.default_realm }.to raise_error(Kerberos::Krb5::Exception)
52
+ expect { Kerberos::Krb5.new(context: context) }
53
+ .to raise_error(Kerberos::Krb5::Exception, /context is closed/)
54
+ expect(krb5.default_realm).to eq(realm)
55
+
56
+ expect { krb5.close }.not_to raise_error
57
+ expect { context.close }.not_to raise_error
58
+ end
59
+
60
+ it 'requires close options to be keywords and rejects unknown keywords' do
61
+ expect { context.close({force: true}) }.to raise_error(ArgumentError)
62
+ expect { context.close(unknown: true) }.to raise_error(ArgumentError, /unknown keyword/)
63
+ end
19
64
  end
20
65
 
21
66
  describe 'constructor options' do
@@ -73,6 +73,9 @@ RSpec.describe Kerberos::Krb5::CredentialsCache do
73
73
  ctx = Kerberos::Krb5::Context.new
74
74
  c = described_class.new(principal: princ, context: ctx)
75
75
  expect(c.primary_principal).to eq(princ)
76
+ expect { ctx.close }.to raise_error(Kerberos::Krb5::Exception, /dependent wrapper/)
77
+ c.close
78
+ expect { ctx.close }.not_to raise_error
76
79
  end
77
80
 
78
81
  it 'raises TypeError for non-Context context argument' do
data/spec/kadm5_spec.rb CHANGED
@@ -66,13 +66,20 @@ RSpec.describe 'Kerberos::Kadm5', :kadm5 do
66
66
  expect { subject.new(principal: user, keytab: 1) }.to raise_error(TypeError)
67
67
  end
68
68
 
69
+ it 'requires a principal when using a keytab' do
70
+ expect { subject.new(keytab: true) }.to raise_error(ArgumentError, /principal must be specified/)
71
+ end
72
+
69
73
  it 'requires service to be a string' do
70
74
  expect { subject.new(principal: user, password: pass, service: 1) }.to raise_error(TypeError)
71
75
  end
72
76
 
73
77
  it 'accepts a context keyword argument' do
74
78
  ctx = Kerberos::Krb5::Context.new
75
- expect { subject.new(principal: user, password: pass, context: ctx) }.not_to raise_error
79
+ kadm5 = subject.new(principal: user, password: pass, context: ctx)
80
+ expect { ctx.close }.to raise_error(Kerberos::Krb5::Exception, /dependent wrapper/)
81
+ kadm5.close
82
+ expect { ctx.close }.not_to raise_error
76
83
  end
77
84
 
78
85
  it 'raises TypeError for non-Context context argument' do
@@ -152,6 +159,27 @@ RSpec.describe 'Kerberos::Kadm5', :kadm5 do
152
159
  end
153
160
  end
154
161
 
162
+ describe '#close' do
163
+ it 'makes all administrative operations fail safely' do
164
+ kadm5 = subject.new(principal: user, password: pass)
165
+ kadm5.close
166
+ policy = Kerberos::Kadm5::Policy.new(name: 'closed_policy')
167
+
168
+ operations = [
169
+ -> { kadm5.get_privileges },
170
+ -> { kadm5.get_principals },
171
+ -> { kadm5.get_policies },
172
+ -> { kadm5.create_policy(policy) },
173
+ -> { kadm5.modify_policy(policy) },
174
+ -> { kadm5.delete_policy('closed_policy') }
175
+ ]
176
+
177
+ operations.each do |operation|
178
+ expect(&operation).to raise_error(Kerberos::Kadm5::Exception, /no administrative context/)
179
+ end
180
+ end
181
+ end
182
+
155
183
  describe '#get_privileges' do
156
184
  before(:each) do
157
185
  @kadm5 = subject.new(principal: user, password: pass)
@@ -223,6 +251,19 @@ RSpec.describe 'Kerberos::Kadm5', :kadm5 do
223
251
  @kadm5.create_policy(name: @policy_name, unknown: true)
224
252
  }.to raise_error(ArgumentError, /unknown keyword/)
225
253
  end
254
+
255
+ it 'modifies values assigned through Policy writers' do
256
+ @kadm5.create_policy(name: @policy_name, min_length: 8, history_num: 1)
257
+ policy = @kadm5.get_policy(@policy_name)
258
+ policy.min_length = 12
259
+ policy.history_num = 3
260
+
261
+ expect(@kadm5.modify_policy(policy)).to equal(@kadm5)
262
+
263
+ modified = @kadm5.get_policy(@policy_name)
264
+ expect(modified.min_length).to eq(12)
265
+ expect(modified.history_num).to eq(3)
266
+ end
226
267
  end
227
268
 
228
269
  describe '#create_principal' do