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.
@@ -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,7 +41,7 @@ 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);
39
46
  free(r);
40
47
  }
@@ -43,9 +50,9 @@ static size_t rkrb5_typed_size(const void *ptr) {
43
50
  return sizeof(RUBY_KRB5);
44
51
  }
45
52
 
46
- static const rb_data_type_t rkrb5_data_type = {
53
+ const rb_data_type_t rkrb5_data_type = {
47
54
  "RUBY_KRB5",
48
- {NULL, rkrb5_typed_free, rkrb5_typed_size,},
55
+ {rkrb5_typed_mark, rkrb5_typed_free, rkrb5_typed_size,},
49
56
  NULL, NULL, RUBY_TYPED_FREE_IMMEDIATELY
50
57
  };
51
58
 
@@ -53,26 +60,63 @@ static const rb_data_type_t rkrb5_data_type = {
53
60
  static VALUE rkrb5_allocate(VALUE klass){
54
61
  RUBY_KRB5* ptr = ALLOC(RUBY_KRB5);
55
62
  memset(ptr, 0, sizeof(RUBY_KRB5));
63
+ ptr->rb_context = Qnil;
56
64
  return TypedData_Wrap_Struct(klass, &rkrb5_data_type, ptr);
57
65
  }
58
66
 
59
67
  /*
60
68
  * call-seq:
61
- * Kerberos::Krb5.new
69
+ * Kerberos::Krb5.new(context: nil)
62
70
  *
63
71
  * Creates and returns a new Kerberos::Krb5 object. This initializes the
64
72
  * context for future method calls on that object.
73
+ *
74
+ * If a +context+ keyword argument is provided, it must be a
75
+ * Kerberos::Krb5::Context object. The context will be borrowed rather than
76
+ * 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
+ *
80
+ * A block form is also supported. If a block is given, the object is
81
+ * yielded to the block and automatically closed when the block returns.
65
82
  */
66
- static VALUE rkrb5_initialize(VALUE self){
83
+ static VALUE rkrb5_initialize(int argc, VALUE* argv, VALUE self){
67
84
  RUBY_KRB5* ptr;
85
+ VALUE v_opts, v_context;
86
+ ID kw_table[1] = { rb_intern("context") };
87
+ VALUE kw_vals[1];
68
88
  krb5_error_code kerror;
69
89
 
70
90
  TypedData_Get_Struct(self, RUBY_KRB5, &rkrb5_data_type, ptr);
71
91
 
72
- kerror = krb5_init_context(&ptr->ctx);
92
+ rb_scan_args(argc, argv, "0:", &v_opts);
73
93
 
74
- if(kerror)
75
- rb_raise(cKrb5Exception, "krb5_init_context: %s", error_message(kerror));
94
+ if(NIL_P(v_opts))
95
+ v_opts = rb_hash_new();
96
+
97
+ rb_get_kwargs(v_opts, kw_table, 0, 1, kw_vals);
98
+ v_context = kw_vals[0] == Qundef ? Qnil : kw_vals[0];
99
+
100
+ 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;
112
+ ptr->rb_context = v_context;
113
+ }
114
+ else{
115
+ kerror = krb5_init_context(&ptr->ctx);
116
+
117
+ if(kerror)
118
+ rb_raise(cKrb5Exception, "krb5_init_context: %s", error_message(kerror));
119
+ }
76
120
 
77
121
  if(rb_block_given_p()){
78
122
  rb_ensure(rb_yield, self, rkrb5_close, self);
@@ -140,7 +184,7 @@ static VALUE rkrb5_set_default_realm(int argc, VALUE* argv, VALUE self){
140
184
  }
141
185
 
142
186
  /* call-seq:
143
- * krb5.get_init_creds_keytab(principal = nil, keytab = nil, service = nil, ccache = nil)
187
+ * krb5.get_init_creds_keytab(principal: nil, keytab: nil, service: nil, ccache: nil)
144
188
  *
145
189
  * Acquire credentials for +principal+ from +keytab+ using +service+. If
146
190
  * no principal is specified, then a principal is derived from the service
@@ -154,7 +198,10 @@ static VALUE rkrb5_set_default_realm(int argc, VALUE* argv, VALUE self){
154
198
  */
155
199
  static VALUE rkrb5_get_init_creds_keytab(int argc, VALUE* argv, VALUE self){
156
200
  RUBY_KRB5* ptr;
157
- VALUE v_user, v_keytab_name, v_service, v_ccache;
201
+ VALUE v_user = Qnil, v_keytab_name = Qnil, v_service = Qnil, v_ccache = Qnil;
202
+ VALUE v_kwargs = Qnil;
203
+ ID kw_table[4] = { rb_intern("principal"), rb_intern("keytab"), rb_intern("service"), rb_intern("ccache") };
204
+ VALUE kw_vals[4];
158
205
  char* user;
159
206
  char* service;
160
207
  char keytab_name[MAX_KEYTAB_NAME_LEN];
@@ -181,7 +228,16 @@ static VALUE rkrb5_get_init_creds_keytab(int argc, VALUE* argv, VALUE self){
181
228
  krb5_free_cred_contents(ptr->ctx, &ptr->creds);
182
229
  memset(&ptr->creds, 0, sizeof(ptr->creds));
183
230
 
184
- rb_scan_args(argc, argv, "04", &v_user, &v_keytab_name, &v_service, &v_ccache);
231
+ rb_scan_args(argc, argv, "0:", &v_kwargs);
232
+
233
+ if(NIL_P(v_kwargs))
234
+ v_kwargs = rb_hash_new();
235
+
236
+ rb_get_kwargs(v_kwargs, kw_table, 0, 4, kw_vals);
237
+ if(kw_vals[0] != Qundef) v_user = kw_vals[0];
238
+ if(kw_vals[1] != Qundef) v_keytab_name = kw_vals[1];
239
+ if(kw_vals[2] != Qundef) v_service = kw_vals[2];
240
+ if(kw_vals[3] != Qundef) v_ccache = kw_vals[3];
185
241
 
186
242
  // Validate argument types before allocating opt, so type errors don't leak it.
187
243
  if(!NIL_P(v_user))
@@ -300,7 +356,7 @@ static VALUE rkrb5_get_init_creds_keytab(int argc, VALUE* argv, VALUE self){
300
356
  *
301
357
  * Example:
302
358
  *
303
- * krb5.get_init_creds_password('foo', 'XXXXXX') # Authenticate 'foo' user
359
+ * krb5.get_init_creds_password(principal: 'foo', password: 'XXXXXX') # Authenticate 'foo' user
304
360
  * krb5.change_password('XXXXXX', 'YYYYYY') # Change password for 'foo'
305
361
  */
306
362
  static VALUE rkrb5_change_password(VALUE self, VALUE v_old, VALUE v_new){
@@ -378,19 +434,26 @@ static VALUE rkrb5_change_password(VALUE self, VALUE v_old, VALUE v_new){
378
434
 
379
435
  /*
380
436
  * call-seq:
381
- * krb5.get_init_creds_password(user, password, service = nil)
437
+ * krb5.get_init_creds_password(principal: nil, password: nil, service: nil, ccache: nil)
382
438
  *
383
- * Authenticates the credentials of +user+ using +password+ against +service+,
439
+ * Authenticates the credentials of +principal+ using +password+ against +service+,
384
440
  * and has the effect of setting the principal and context internally. This method
385
441
  * must typically be called before using other methods.
442
+ *
443
+ * When +ccache+ is given, the acquired credentials will be written into the
444
+ * provided Kerberos::Krb5::CredentialsCache.
386
445
  */
387
446
  static VALUE rkrb5_get_init_creds_passwd(int argc, VALUE* argv, VALUE self){
388
447
  RUBY_KRB5* ptr;
389
- VALUE v_user, v_pass, v_service;
448
+ VALUE v_user = Qnil, v_pass = Qnil, v_service = Qnil, v_ccache = Qnil;
449
+ VALUE v_kwargs = Qnil;
450
+ ID kw_table[4] = { rb_intern("principal"), rb_intern("password"), rb_intern("service"), rb_intern("ccache") };
451
+ VALUE kw_vals[4];
390
452
  char* user;
391
453
  char* pass;
392
454
  char* service;
393
455
  krb5_error_code kerror;
456
+ krb5_get_init_creds_opt* opt = NULL;
394
457
 
395
458
  TypedData_Get_Struct(self, RUBY_KRB5, &rkrb5_data_type, ptr);
396
459
 
@@ -406,7 +469,19 @@ static VALUE rkrb5_get_init_creds_passwd(int argc, VALUE* argv, VALUE self){
406
469
  krb5_free_cred_contents(ptr->ctx, &ptr->creds);
407
470
  memset(&ptr->creds, 0, sizeof(ptr->creds));
408
471
 
409
- rb_scan_args(argc, argv, "21", &v_user, &v_pass, &v_service);
472
+ rb_scan_args(argc, argv, "0:", &v_kwargs);
473
+
474
+ if(NIL_P(v_kwargs))
475
+ v_kwargs = rb_hash_new();
476
+
477
+ rb_get_kwargs(v_kwargs, kw_table, 0, 4, kw_vals);
478
+ if(kw_vals[0] != Qundef) v_user = kw_vals[0];
479
+ if(kw_vals[1] != Qundef) v_pass = kw_vals[1];
480
+ if(kw_vals[2] != Qundef) v_service = kw_vals[2];
481
+ if(kw_vals[3] != Qundef) v_ccache = kw_vals[3];
482
+
483
+ if(NIL_P(v_user) || NIL_P(v_pass))
484
+ rb_raise(rb_eArgError, "principal and password are required");
410
485
 
411
486
  Check_Type(v_user, T_STRING);
412
487
  Check_Type(v_pass, T_STRING);
@@ -426,6 +501,21 @@ static VALUE rkrb5_get_init_creds_passwd(int argc, VALUE* argv, VALUE self){
426
501
  if(kerror)
427
502
  rb_raise(cKrb5Exception, "krb5_parse_name: %s", error_message(kerror));
428
503
 
504
+ if(!NIL_P(v_ccache)){
505
+ RUBY_KRB5_CCACHE* ccptr;
506
+ TypedData_Get_Struct(v_ccache, RUBY_KRB5_CCACHE, &rkrb5_ccache_data_type, ccptr);
507
+
508
+ kerror = krb5_get_init_creds_opt_alloc(ptr->ctx, &opt);
509
+ if(kerror)
510
+ rb_raise(cKrb5Exception, "krb5_get_init_creds_opt_alloc: %s", error_message(kerror));
511
+
512
+ kerror = krb5_get_init_creds_opt_set_out_ccache(ptr->ctx, opt, ccptr->ccache);
513
+ if(kerror){
514
+ krb5_get_init_creds_opt_free(ptr->ctx, opt);
515
+ rb_raise(cKrb5Exception, "krb5_get_init_creds_opt_set_out_ccache: %s", error_message(kerror));
516
+ }
517
+ }
518
+
429
519
  kerror = krb5_get_init_creds_password(
430
520
  ptr->ctx,
431
521
  &ptr->creds,
@@ -435,9 +525,12 @@ static VALUE rkrb5_get_init_creds_passwd(int argc, VALUE* argv, VALUE self){
435
525
  NULL,
436
526
  0,
437
527
  service,
438
- NULL
528
+ opt
439
529
  );
440
530
 
531
+ if(opt)
532
+ krb5_get_init_creds_opt_free(ptr->ctx, opt);
533
+
441
534
  if(kerror)
442
535
  rb_raise(cKrb5Exception, "krb5_get_init_creds_password: %s", error_message(kerror));
443
536
 
@@ -446,7 +539,7 @@ static VALUE rkrb5_get_init_creds_passwd(int argc, VALUE* argv, VALUE self){
446
539
 
447
540
  /*
448
541
  * call-seq:
449
- * krb5.authenticate!(user, password, service = nil)
542
+ * krb5.authenticate!(principal:, password:, service: nil)
450
543
  *
451
544
  * Convenience method that: acquires initial credentials via password and
452
545
  * immediately verifies those credentials using `verify_init_creds` with
@@ -458,7 +551,9 @@ static VALUE rkrb5_get_init_creds_passwd(int argc, VALUE* argv, VALUE self){
458
551
  */
459
552
  static VALUE rkrb5_authenticate_bang(int argc, VALUE* argv, VALUE self){
460
553
  RUBY_KRB5* ptr;
461
- VALUE v_user, v_pass, v_service;
554
+ VALUE v_kwargs, v_user, v_pass, v_service;
555
+ ID kw_table[3] = { rb_intern("principal"), rb_intern("password"), rb_intern("service") };
556
+ VALUE kw_vals[3];
462
557
  char* user;
463
558
  char* pass;
464
559
  char* service;
@@ -479,8 +574,15 @@ static VALUE rkrb5_authenticate_bang(int argc, VALUE* argv, VALUE self){
479
574
  krb5_free_cred_contents(ptr->ctx, &ptr->creds);
480
575
  memset(&ptr->creds, 0, sizeof(ptr->creds));
481
576
 
482
- // Require user and password, optional service
483
- rb_scan_args(argc, argv, "21", &v_user, &v_pass, &v_service);
577
+ rb_scan_args(argc, argv, "0:", &v_kwargs);
578
+
579
+ if(NIL_P(v_kwargs))
580
+ v_kwargs = rb_hash_new();
581
+
582
+ rb_get_kwargs(v_kwargs, kw_table, 2, 1, kw_vals);
583
+ v_user = kw_vals[0];
584
+ v_pass = kw_vals[1];
585
+ v_service = kw_vals[2] == Qundef ? Qnil : kw_vals[2];
484
586
 
485
587
  Check_Type(v_user, T_STRING);
486
588
  Check_Type(v_pass, T_STRING);
@@ -580,11 +682,12 @@ static VALUE rkrb5_close(VALUE self){
580
682
  if(ptr->princ)
581
683
  krb5_free_principal(ptr->ctx, ptr->princ);
582
684
 
583
- if(ptr->ctx)
685
+ if(ptr->ctx && ptr->rb_context == Qnil)
584
686
  krb5_free_context(ptr->ctx);
585
687
 
586
688
  ptr->ctx = NULL;
587
689
  ptr->princ = NULL;
690
+ ptr->rb_context = Qnil;
588
691
 
589
692
  return Qtrue;
590
693
  }
@@ -701,7 +804,7 @@ static VALUE rkrb5_get_permitted_enctypes(VALUE self){
701
804
 
702
805
  /*
703
806
  * call-seq:
704
- * krb5.verify_init_creds(server = nil, keytab = nil, ccache = nil)
807
+ * krb5.verify_init_creds(server: nil, keytab: nil, ccache: nil)
705
808
  *
706
809
  * Verifies the initial credentials currently stored in the internal
707
810
  * credentials structure. Optionally a server principal string, a
@@ -711,7 +814,10 @@ static VALUE rkrb5_get_permitted_enctypes(VALUE self){
711
814
  */
712
815
  static VALUE rkrb5_verify_init_creds(int argc, VALUE* argv, VALUE self){
713
816
  RUBY_KRB5* ptr;
714
- VALUE v_server, v_keytab, v_ccache;
817
+ VALUE v_server = Qnil, v_keytab = Qnil, v_ccache = Qnil;
818
+ VALUE v_kwargs = Qnil;
819
+ ID kw_table[3] = { rb_intern("server"), rb_intern("keytab"), rb_intern("ccache") };
820
+ VALUE kw_vals[3];
715
821
  krb5_error_code kerror;
716
822
  krb5_principal server_princ = NULL;
717
823
  RUBY_KRB5_KEYTAB* ktptr = NULL;
@@ -719,7 +825,15 @@ static VALUE rkrb5_verify_init_creds(int argc, VALUE* argv, VALUE self){
719
825
  krb5_keytab keytab = NULL;
720
826
  krb5_ccache *ccache_ptr = NULL;
721
827
 
722
- rb_scan_args(argc, argv, "03", &v_server, &v_keytab, &v_ccache);
828
+ rb_scan_args(argc, argv, "0:", &v_kwargs);
829
+
830
+ if(NIL_P(v_kwargs))
831
+ v_kwargs = rb_hash_new();
832
+
833
+ rb_get_kwargs(v_kwargs, kw_table, 0, 3, kw_vals);
834
+ if(kw_vals[0] != Qundef) v_server = kw_vals[0];
835
+ if(kw_vals[1] != Qundef) v_keytab = kw_vals[1];
836
+ if(kw_vals[2] != Qundef) v_ccache = kw_vals[2];
723
837
 
724
838
  TypedData_Get_Struct(self, RUBY_KRB5, &rkrb5_data_type, ptr);
725
839
 
@@ -781,6 +895,83 @@ static VALUE rkrb5_verify_init_creds(int argc, VALUE* argv, VALUE self){
781
895
  return Qtrue;
782
896
  }
783
897
 
898
+ /*
899
+ * call-seq:
900
+ * krb5.get_host_realm(hostname) -> Array
901
+ *
902
+ * Returns an array of realm names associated with +hostname+.
903
+ * Uses the Kerberos library's host-to-realm mapping (krb5.conf
904
+ * [domain_realm] section, DNS lookups, etc.).
905
+ */
906
+ static VALUE rkrb5_get_host_realm(VALUE self, VALUE v_host){
907
+ RUBY_KRB5* ptr;
908
+ char** realms;
909
+ char** p;
910
+ krb5_error_code kerror;
911
+
912
+ TypedData_Get_Struct(self, RUBY_KRB5, &rkrb5_data_type, ptr);
913
+
914
+ if(!ptr->ctx)
915
+ rb_raise(cKrb5Exception, "no context has been established");
916
+
917
+ Check_Type(v_host, T_STRING);
918
+
919
+ kerror = krb5_get_host_realm(ptr->ctx, StringValueCStr(v_host), &realms);
920
+
921
+ if(kerror)
922
+ rb_raise(cKrb5Exception, "krb5_get_host_realm: %s", error_message(kerror));
923
+
924
+ VALUE v_array = rb_ary_new();
925
+
926
+ for(p = realms; p && *p; p++)
927
+ rb_ary_push(v_array, rb_str_new2(*p));
928
+
929
+ krb5_free_host_realm(ptr->ctx, realms);
930
+
931
+ return v_array;
932
+ }
933
+
934
+ /*
935
+ * call-seq:
936
+ * krb5.expand_hostname(hostname) -> String
937
+ *
938
+ * Canonicalizes +hostname+ by performing a forward DNS lookup followed
939
+ * by a reverse lookup, returning the canonical hostname.
940
+ */
941
+ static VALUE rkrb5_expand_hostname(VALUE self, VALUE v_host){
942
+ RUBY_KRB5* ptr;
943
+ char* canonical;
944
+ krb5_error_code kerror;
945
+
946
+ TypedData_Get_Struct(self, RUBY_KRB5, &rkrb5_data_type, ptr);
947
+
948
+ if(!ptr->ctx)
949
+ rb_raise(cKrb5Exception, "no context has been established");
950
+
951
+ Check_Type(v_host, T_STRING);
952
+
953
+ kerror = krb5_expand_hostname(ptr->ctx, StringValueCStr(v_host), &canonical);
954
+
955
+ if(kerror)
956
+ rb_raise(cKrb5Exception, "krb5_expand_hostname: %s", error_message(kerror));
957
+
958
+ VALUE v_result = rb_str_new2(canonical);
959
+ krb5_free_string(ptr->ctx, canonical);
960
+
961
+ return v_result;
962
+ }
963
+
964
+ /*
965
+ * call-seq:
966
+ * Kerberos::Krb5.thread_safe?
967
+ *
968
+ * Returns true if the Kerberos library was built with multithread support,
969
+ * false otherwise.
970
+ */
971
+ static VALUE rkrb5_s_thread_safe(VALUE klass){
972
+ return krb5_is_thread_safe() ? Qtrue : Qfalse;
973
+ }
974
+
784
975
  void Init_rkerberos(void){
785
976
  mKerberos = rb_define_module("Kerberos");
786
977
  cKrb5 = rb_define_class_under(mKerberos, "Krb5", rb_cObject);
@@ -790,7 +981,10 @@ void Init_rkerberos(void){
790
981
  rb_define_alloc_func(cKrb5, rkrb5_allocate);
791
982
 
792
983
  // Initializers
793
- rb_define_method(cKrb5, "initialize", rkrb5_initialize, 0);
984
+ rb_define_method(cKrb5, "initialize", rkrb5_initialize, -1);
985
+
986
+ // Singleton Methods
987
+ rb_define_singleton_method(cKrb5, "thread_safe?", rkrb5_s_thread_safe, 0);
794
988
 
795
989
  // Krb5 Methods
796
990
  rb_define_method(cKrb5, "authenticate!", rkrb5_authenticate_bang, -1);
@@ -803,13 +997,20 @@ void Init_rkerberos(void){
803
997
  rb_define_method(cKrb5, "get_permitted_enctypes", rkrb5_get_permitted_enctypes, 0);
804
998
  rb_define_method(cKrb5, "set_default_realm", rkrb5_set_default_realm, -1);
805
999
  rb_define_method(cKrb5, "verify_init_creds", rkrb5_verify_init_creds, -1);
1000
+ rb_define_method(cKrb5, "get_host_realm", rkrb5_get_host_realm, 1);
1001
+ rb_define_method(cKrb5, "expand_hostname", rkrb5_expand_hostname, 1);
806
1002
 
807
1003
  // Aliases
808
1004
  rb_define_alias(cKrb5, "default_realm", "get_default_realm");
1005
+ rb_define_alias(cKrb5, "default_realm=", "set_default_realm");
809
1006
  rb_define_alias(cKrb5, "default_principal", "get_default_principal");
1007
+ rb_define_alias(cKrb5, "host_realm", "get_host_realm");
1008
+ rb_define_alias(cKrb5, "init_creds_password", "get_init_creds_password");
1009
+ rb_define_alias(cKrb5, "init_creds_keytab", "get_init_creds_keytab");
1010
+ rb_define_alias(cKrb5, "permitted_enctypes", "get_permitted_enctypes");
810
1011
 
811
- /* 0.2.3: The version of the custom rkerberos library */
812
- rb_define_const(cKrb5, "VERSION", rb_str_new2("0.2.3"));
1012
+ /* 0.3.0: The version of the custom rkerberos library */
1013
+ rb_define_const(cKrb5, "VERSION", rb_str_new2("0.3.0"));
813
1014
 
814
1015
  // Encoding type constants
815
1016
 
@@ -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
 
@@ -67,6 +69,7 @@ typedef struct {
67
69
  krb5_creds creds;
68
70
  krb5_principal princ;
69
71
  krb5_keytab keytab;
72
+ VALUE rb_context;
70
73
  } RUBY_KRB5;
71
74
 
72
75
  // Kerberos::Context
@@ -81,6 +84,7 @@ typedef struct {
81
84
  krb5_principal princ;
82
85
  void* handle;
83
86
  char** db_args;
87
+ VALUE rb_context; // Ruby Context object when borrowed, Qnil if ctx is owned
84
88
  } RUBY_KADM5;
85
89
 
86
90
  // Kerberos::Krb5::Keytab::Entry
@@ -96,23 +100,27 @@ typedef struct {
96
100
  krb5_context ctx;
97
101
  krb5_creds creds;
98
102
  krb5_keytab keytab;
103
+ VALUE rb_context; // Ruby Context object when borrowed, Qnil if ctx is owned
99
104
  } RUBY_KRB5_KEYTAB;
100
105
 
101
106
  typedef struct {
102
107
  krb5_context ctx;
103
108
  krb5_principal principal;
109
+ VALUE rb_context; // Ruby Context object when borrowed, Qnil if ctx is owned
104
110
  } RUBY_KRB5_PRINC;
105
111
 
106
112
  typedef struct {
107
113
  krb5_context ctx;
108
114
  krb5_ccache ccache;
109
115
  krb5_principal principal;
116
+ VALUE rb_context; // Ruby Context object when borrowed, Qnil if ctx is owned
110
117
  } RUBY_KRB5_CCACHE;
111
118
 
112
119
  #ifdef HAVE_KADM5_ADMIN_H
113
120
  typedef struct {
114
121
  krb5_context ctx;
115
122
  kadm5_config_params config;
123
+ VALUE rb_context; // Ruby Context object when borrowed, Qnil if ctx is owned
116
124
  } RUBY_KADM5_CONFIG;
117
125
 
118
126
  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.0'
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
  }
data/spec/config_spec.rb CHANGED
@@ -11,6 +11,39 @@ RSpec.describe 'Kerberos::Kadm5::Config', :kadm5 do
11
11
  expect(config).to be_frozen
12
12
  end
13
13
 
14
+ describe 'constructor' do
15
+ it 'can be called with no arguments' do
16
+ expect { klass.new }.not_to raise_error
17
+ end
18
+
19
+ it 'accepts a context option' do
20
+ ctx = Kerberos::Krb5::Context.new
21
+ expect { klass.new(context: ctx) }.not_to raise_error
22
+ end
23
+
24
+ it 'returns same realm with or without context' do
25
+ ctx = Kerberos::Krb5::Context.new
26
+ c1 = klass.new
27
+ c2 = klass.new(context: ctx)
28
+ expect(c2.realm).to eq(c1.realm)
29
+ end
30
+
31
+ it 'raises TypeError for non-Context context argument' do
32
+ expect { klass.new(context: "bad") }.to raise_error(TypeError)
33
+ end
34
+
35
+ it 'raises error for a closed context' do
36
+ ctx = Kerberos::Krb5::Context.new
37
+ ctx.close
38
+ expect { klass.new(context: ctx) }.to raise_error(Kerberos::Krb5::Exception)
39
+ end
40
+
41
+ it 'rejects positional option hashes and unknown keywords' do
42
+ expect { klass.new({context: nil}) }.to raise_error(ArgumentError)
43
+ expect { klass.new(unknown: true) }.to raise_error(ArgumentError, /unknown keyword/)
44
+ end
45
+ end
46
+
14
47
  describe 'realm' do
15
48
  it 'responds to realm' do
16
49
  expect(config).to respond_to(:realm)
data/spec/context_spec.rb CHANGED
@@ -51,6 +51,57 @@ RSpec.describe Kerberos::Krb5::Context do
51
51
  expect(ctx).to be_a(described_class)
52
52
  expect { ctx.close }.not_to raise_error
53
53
  end
54
+
55
+ it 'rejects positional option hashes and unknown keywords' do
56
+ expect { described_class.new({secure: true}) }.to raise_error(ArgumentError)
57
+ expect { described_class.new(unknown: true) }.to raise_error(ArgumentError, /unknown keyword/)
58
+ end
59
+ end
60
+
61
+ describe '#default_realm' do
62
+ it 'responds to default_realm' do
63
+ expect(context).to respond_to(:default_realm)
64
+ end
65
+
66
+ it 'returns a string' do
67
+ expect(context.default_realm).to be_a(String)
68
+ end
69
+
70
+ it 'returns a non-empty realm' do
71
+ expect(context.default_realm.size).to be > 0
72
+ end
73
+
74
+ it 'raises when context is closed' do
75
+ context.close
76
+ expect { context.default_realm }.to raise_error(Kerberos::Krb5::Exception)
77
+ end
78
+ end
79
+
80
+ describe '#default_realm=' do
81
+ it 'responds to default_realm=' do
82
+ expect(context).to respond_to(:default_realm=)
83
+ end
84
+
85
+ it 'can set and read back a custom realm' do
86
+ context.default_realm = 'TEST.REALM'
87
+ expect(context.default_realm).to eq('TEST.REALM')
88
+ end
89
+
90
+ it 'accepts nil to reset to the default' do
91
+ original = context.default_realm
92
+ context.default_realm = 'TEMP.REALM'
93
+ context.default_realm = nil
94
+ expect(context.default_realm).to eq(original)
95
+ end
96
+
97
+ it 'raises TypeError for non-string argument' do
98
+ expect { context.default_realm = 123 }.to raise_error(TypeError)
99
+ end
100
+
101
+ it 'raises when context is closed' do
102
+ context.close
103
+ expect { context.default_realm = 'X' }.to raise_error(Kerberos::Krb5::Exception)
104
+ end
54
105
  end
55
106
 
56
107
  after(:each) do