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.
data/MANIFEST.md CHANGED
@@ -1,24 +1,29 @@
1
1
  * CHANGES.md
2
+ * EXAMPLES.md
3
+ * Gemfile
4
+ * LICENSE
2
5
  * rkerberos.gemspec
3
6
  * MANIFEST.md
4
7
  * Rakefile
5
- * README
6
- * ext/ccache.c
7
- * ext/context.c
8
- * ext/extconf.rb
9
- * ext/kadm5.c
10
- * ext/keytab.c
11
- * ext/keytab_entry.c
12
- * ext/rkerberos.c
13
- * ext/rkerberos.h
14
- * ext/policy.c
15
- * ext/principal.c
8
+ * README.md
9
+ * ext/rkerberos/ccache.c
10
+ * ext/rkerberos/config.c
11
+ * ext/rkerberos/context.c
12
+ * ext/rkerberos/extconf.rb
13
+ * ext/rkerberos/kadm5.c
14
+ * ext/rkerberos/keytab.c
15
+ * ext/rkerberos/keytab_entry.c
16
+ * ext/rkerberos/policy.c
17
+ * ext/rkerberos/principal.c
18
+ * ext/rkerberos/rkerberos.c
19
+ * ext/rkerberos/rkerberos.h
16
20
  * spec/config_spec.rb
17
21
  * spec/context_spec.rb
18
- * spec/credentials_spec.rb
22
+ * spec/credentials_cache_spec.rb
19
23
  * spec/kadm5_spec.rb
20
24
  * spec/keytab_entry_spec.rb
21
25
  * spec/krb5_keytab_spec.rb
22
26
  * spec/krb5_spec.rb
23
27
  * spec/policy_spec.rb
24
28
  * spec/principal_spec.rb
29
+ * spec/spec_helper.rb
data/README.md CHANGED
@@ -18,17 +18,22 @@ puts krb.get_permitted_enctypes.keys.join(',')
18
18
 
19
19
  # Credentials cache
20
20
  cc = Kerberos::Krb5::CredentialsCache.new
21
- krb.verify_init_creds(nil, nil, cc)
21
+ krb.get_init_creds_password(
22
+ principal: ENV['KRB5_PRINCIPAL'],
23
+ password: ENV['KRB5_PASSWORD'],
24
+ ccache: cc
25
+ )
26
+ krb.verify_init_creds
22
27
  puts cc.primary_principal
23
28
 
24
29
  # Keytab
25
30
  kt_name = Kerberos::Krb5::Keytab.new.default_name # e.g. "FILE:/etc/krb5.keytab"
26
- krb.get_init_creds_keytab('host/server.example.com', kt_name)
27
- krb.get_init_creds_keytab('host/server.example.com', kt_name, nil, cc) # or write to cache
31
+ krb.get_init_creds_keytab(principal: 'host/server.example.com', keytab: kt_name)
32
+ krb.get_init_creds_keytab(principal: 'host/server.example.com', keytab: kt_name, ccache: cc) # or write to cache
28
33
 
29
34
  # Admin
30
35
  Kerberos::Kadm5.new(principal: ENV['KRB5_ADMIN_PRINCIPAL'], password: ENV['KRB5_ADMIN_PASSWORD']) do |kadmin|
31
- kadmin.create_principal('newuser@EXAMPLE.COM', 'initialpass')
36
+ kadmin.create_principal(name: 'newuser@EXAMPLE.COM', password: 'initialpass')
32
37
  kadmin.set_password('newuser@EXAMPLE.COM', 'betterpass')
33
38
  kadmin.delete_principal('newuser@EXAMPLE.COM')
34
39
  end
@@ -38,6 +43,13 @@ ctx = Kerberos::Krb5::Context.new # standard context
38
43
  ctx = Kerberos::Krb5::Context.new(profile: '/etc/krb5.conf') # or use a profile
39
44
  ctx = Kerberos::Krb5::Context.new(secure: true) # or use a secure context
40
45
  ctx.close
46
+
47
+ # Contexts with active dependent wrappers reject close. A forced close stops
48
+ # direct context use immediately and defers native cleanup until dependents close.
49
+ ctx = Kerberos::Krb5::Context.new
50
+ krb = Kerberos::Krb5.new(context: ctx)
51
+ ctx.close(force: true)
52
+ krb.close
41
53
  ```
42
54
 
43
55
  # Requirements
@@ -79,7 +91,7 @@ ctx.close
79
91
  # Testing
80
92
 
81
93
  ## Prerequisites
82
- - Ruby 3.4 or later
94
+ - Ruby 3.2 or later
83
95
  - Docker or Podman
84
96
  - docker-compose or podman-compose
85
97
 
@@ -160,7 +172,6 @@ The test environment includes:
160
172
  # TODO
161
173
  * Create a separate class for the replay cache.
162
174
  * Better credentials cache support.
163
- * Ability to add and delete keytab entries.
164
175
 
165
176
  # Authors
166
177
  * Daniel Berger
@@ -4,6 +4,13 @@ VALUE cKrb5CCache;
4
4
 
5
5
 
6
6
  // TypedData functions for RUBY_KRB5_CCACHE
7
+ static void rkrb5_ccache_typed_mark(void *ptr) {
8
+ if (!ptr) return;
9
+ RUBY_KRB5_CCACHE *c = (RUBY_KRB5_CCACHE *)ptr;
10
+ if (c->rb_context != Qnil)
11
+ rb_gc_mark(c->rb_context);
12
+ }
13
+
7
14
  static void rkrb5_ccache_typed_free(void *ptr) {
8
15
  if (!ptr) return;
9
16
  RUBY_KRB5_CCACHE *c = (RUBY_KRB5_CCACHE *)ptr;
@@ -11,8 +18,10 @@ static void rkrb5_ccache_typed_free(void *ptr) {
11
18
  krb5_cc_close(c->ctx, c->ccache);
12
19
  if (c->principal)
13
20
  krb5_free_principal(c->ctx, c->principal);
14
- if (c->ctx)
21
+ if (c->ctx && c->rb_context == Qnil)
15
22
  krb5_free_context(c->ctx);
23
+ else if (c->rb_context != Qnil)
24
+ rkrb5_context_release(c->rb_context);
16
25
  free(c);
17
26
  }
18
27
 
@@ -22,7 +31,7 @@ static size_t rkrb5_ccache_typed_size(const void *ptr) {
22
31
 
23
32
  const rb_data_type_t rkrb5_ccache_data_type = {
24
33
  "RUBY_KRB5_CCACHE",
25
- {NULL, rkrb5_ccache_typed_free, rkrb5_ccache_typed_size,},
34
+ {rkrb5_ccache_typed_mark, rkrb5_ccache_typed_free, rkrb5_ccache_typed_size,},
26
35
  NULL, NULL, RUBY_TYPED_FREE_IMMEDIATELY
27
36
  };
28
37
 
@@ -30,20 +39,28 @@ const rb_data_type_t rkrb5_ccache_data_type = {
30
39
  static VALUE rkrb5_ccache_allocate(VALUE klass){
31
40
  RUBY_KRB5_CCACHE* ptr = ALLOC(RUBY_KRB5_CCACHE);
32
41
  memset(ptr, 0, sizeof(RUBY_KRB5_CCACHE));
42
+ ptr->rb_context = Qnil;
33
43
  return TypedData_Wrap_Struct(klass, &rkrb5_ccache_data_type, ptr);
34
44
  }
35
45
 
36
46
  /*
37
47
  * call-seq:
38
- * Kerberos::CredentialsCache.new(principal = nil, cache_name = nil)
48
+ * Kerberos::CredentialsCache.new(principal: nil, cache_name: nil, context: nil)
49
+ *
50
+ * Creates and returns a new Kerberos::CredentialsCache object. Accepts the
51
+ * following keyword arguments:
39
52
  *
40
- * Creates and returns a new Kerberos::CredentialsCache object. If cache_name
41
- * is specified, then that cache is used, which must be in "type:residual"
42
- * format, where 'type' is a type known to Kerberos (typically 'FILE').
53
+ * - +principal+: A string principal name. If specified, the credentials cache
54
+ * is created or refreshed with this as the primary principal. If a cache
55
+ * already exists, its contents are destroyed. May also be an object that
56
+ * responds to the `.principal` method.
43
57
  *
44
- * If a +principal+ is specified, then it creates or refreshes the credentials
45
- * cache with the primary principal set to +principal+. If the credentials
46
- * cache already exists, its contents are destroyed.
58
+ * - +cache_name+: The name of the credentials cache to use, which must be in
59
+ * "type:residual" format, where 'type' is a type known to Kerberos
60
+ * (typically 'FILE'). If omitted, the default cache is used.
61
+ *
62
+ * - +context+: A Kerberos::Krb5::Context object. If provided, that context is
63
+ * used instead of creating a new one via krb5_init_context.
47
64
  *
48
65
  * Note that the principal's credentials are not set via the constructor.
49
66
  * It merely creates the cache and sets the default principal.
@@ -51,23 +68,45 @@ static VALUE rkrb5_ccache_allocate(VALUE klass){
51
68
  static VALUE rkrb5_ccache_initialize(int argc, VALUE* argv, VALUE self){
52
69
  RUBY_KRB5_CCACHE* ptr;
53
70
  krb5_error_code kerror;
54
- VALUE v_principal, v_name;
71
+ VALUE v_opts, v_principal, v_name, v_context;
72
+ ID kw_table[3] = { rb_intern("principal"), rb_intern("cache_name"), rb_intern("context") };
73
+ VALUE kw_vals[3];
55
74
 
56
75
  TypedData_Get_Struct(self, RUBY_KRB5_CCACHE, &rkrb5_ccache_data_type, ptr);
57
76
 
58
- rb_scan_args(argc, argv, "02", &v_principal, &v_name);
77
+ rb_scan_args(argc, argv, "0:", &v_opts);
78
+
79
+ if(NIL_P(v_opts))
80
+ v_opts = rb_hash_new();
81
+
82
+ rb_get_kwargs(v_opts, kw_table, 0, 3, kw_vals);
83
+ v_principal = kw_vals[0] == Qundef ? Qnil : kw_vals[0];
84
+ v_name = kw_vals[1] == Qundef ? Qnil : kw_vals[1];
85
+ v_context = kw_vals[2] == Qundef ? Qnil : kw_vals[2];
86
+
87
+ if(!NIL_P(v_principal)){
88
+ if(rb_respond_to(v_principal, rb_intern("principal")))
89
+ v_principal = rb_funcall(v_principal, rb_intern("principal"), 0);
59
90
 
60
- if(RTEST(v_principal))
61
91
  Check_Type(v_principal, T_STRING);
92
+ }
62
93
 
63
- // Initialize the context
64
- kerror = krb5_init_context(&ptr->ctx);
94
+ // Initialize or borrow the context
95
+ if(!NIL_P(v_context)){
96
+ ptr->ctx = rkrb5_context_borrow(v_context);
97
+ ptr->rb_context = v_context;
98
+ }
99
+ else{
100
+ kerror = krb5_init_context(&ptr->ctx);
65
101
 
66
- if(kerror)
67
- rb_raise(cKrb5Exception, "krb5_init_context: %s", error_message(kerror));
102
+ if(kerror)
103
+ rb_raise(cKrb5Exception, "krb5_init_context: %s", error_message(kerror));
104
+
105
+ ptr->rb_context = Qnil;
106
+ }
68
107
 
69
108
  // Convert the principal name to a principal object
70
- if(RTEST(v_principal)){
109
+ if(!NIL_P(v_principal)){
71
110
  kerror = krb5_parse_name(
72
111
  ptr->ctx,
73
112
  StringValueCStr(v_principal),
@@ -127,12 +166,15 @@ static VALUE rkrb5_ccache_close(VALUE self){
127
166
  if(ptr->principal)
128
167
  krb5_free_principal(ptr->ctx, ptr->principal);
129
168
 
130
- if(ptr->ctx)
169
+ if(ptr->ctx && ptr->rb_context == Qnil)
131
170
  krb5_free_context(ptr->ctx);
171
+ else if(ptr->rb_context != Qnil)
172
+ rkrb5_context_release(ptr->rb_context);
132
173
 
133
174
  ptr->ccache = NULL;
134
175
  ptr->ctx = NULL;
135
176
  ptr->principal = NULL;
177
+ ptr->rb_context = Qnil;
136
178
 
137
179
  return self;
138
180
  }
@@ -228,11 +270,6 @@ static VALUE rkrb5_ccache_primary_principal(VALUE self){
228
270
  return v_name;
229
271
  }
230
272
 
231
- // Simple wrapper around krb5_cc_get_principal returning a principal name string.
232
- static VALUE rkrb5_ccache_principal(VALUE self){
233
- return rkrb5_ccache_primary_principal(self);
234
- }
235
-
236
273
  /*
237
274
  * call-seq:
238
275
  * ccache.destroy
@@ -264,12 +301,15 @@ static VALUE rkrb5_ccache_destroy(VALUE self){
264
301
  if(ptr->principal)
265
302
  krb5_free_principal(ptr->ctx, ptr->principal);
266
303
 
267
- if(ptr->ctx)
304
+ if(ptr->ctx && ptr->rb_context == Qnil)
268
305
  krb5_free_context(ptr->ctx);
306
+ else if(ptr->rb_context != Qnil)
307
+ rkrb5_context_release(ptr->rb_context);
269
308
 
270
309
  ptr->ccache = NULL;
271
310
  ptr->ctx = NULL;
272
311
  ptr->principal = NULL;
312
+ ptr->rb_context = Qnil;
273
313
 
274
314
  rb_raise(cKrb5Exception, "krb5_cc_destroy: %s", error_message(kerror));
275
315
  }
@@ -278,12 +318,15 @@ static VALUE rkrb5_ccache_destroy(VALUE self){
278
318
  if(ptr->principal)
279
319
  krb5_free_principal(ptr->ctx, ptr->principal);
280
320
 
281
- if(ptr->ctx)
321
+ if(ptr->ctx && ptr->rb_context == Qnil)
282
322
  krb5_free_context(ptr->ctx);
323
+ else if(ptr->rb_context != Qnil)
324
+ rkrb5_context_release(ptr->rb_context);
283
325
 
284
326
  ptr->ccache = NULL;
285
327
  ptr->ctx = NULL;
286
328
  ptr->principal = NULL;
329
+ ptr->rb_context = Qnil;
287
330
 
288
331
  return v_bool;
289
332
  }
@@ -329,6 +372,35 @@ static VALUE rkrb5_ccache_dup(VALUE self){
329
372
  return newobj;
330
373
  }
331
374
 
375
+ /*
376
+ * call-seq:
377
+ * ccache.full_name -> String
378
+ *
379
+ * Returns the full name of the credential cache, including the type prefix,
380
+ * e.g. "FILE:/tmp/krb5cc_1000".
381
+ */
382
+ static VALUE rkrb5_ccache_full_name(VALUE self){
383
+ RUBY_KRB5_CCACHE* ptr;
384
+ char *full_name;
385
+ krb5_error_code kerror;
386
+ VALUE result;
387
+
388
+ TypedData_Get_Struct(self, RUBY_KRB5_CCACHE, &rkrb5_ccache_data_type, ptr);
389
+
390
+ if(!ptr->ctx)
391
+ rb_raise(cKrb5Exception, "no context has been established");
392
+
393
+ kerror = krb5_cc_get_full_name(ptr->ctx, ptr->ccache, &full_name);
394
+
395
+ if(kerror)
396
+ rb_raise(cKrb5Exception, "krb5_cc_get_full_name: %s", error_message(kerror));
397
+
398
+ result = rb_str_new2(full_name);
399
+ krb5_free_string(ptr->ctx, full_name);
400
+
401
+ return result;
402
+ }
403
+
332
404
  void Init_ccache(void){
333
405
  /* The Kerberos::Krb5::CredentialsCache class encapsulates a Kerberos credentials cache. */
334
406
  cKrb5CCache = rb_define_class_under(cKrb5, "CredentialsCache", rb_cObject);
@@ -346,10 +418,11 @@ void Init_ccache(void){
346
418
  rb_define_method(cKrb5CCache, "cache_type", rkrb5_ccache_get_type, 0);
347
419
  rb_define_method(cKrb5CCache, "destroy", rkrb5_ccache_destroy, 0);
348
420
  rb_define_method(cKrb5CCache, "primary_principal", rkrb5_ccache_primary_principal, 0);
349
- rb_define_method(cKrb5CCache, "principal", rkrb5_ccache_principal, 0);
421
+ rb_define_method(cKrb5CCache, "full_name", rkrb5_ccache_full_name, 0);
350
422
  rb_define_method(cKrb5CCache, "dup", rkrb5_ccache_dup, 0);
351
423
  rb_define_alias(cKrb5CCache, "clone", "dup");
352
424
 
353
425
  // Aliases
354
426
  rb_define_alias(cKrb5CCache, "delete", "destroy");
427
+ rb_define_alias(cKrb5CCache, "principal", "primary_principal");
355
428
  }
@@ -4,15 +4,35 @@
4
4
  VALUE cKadm5Config;
5
5
  VALUE cKeySalt;
6
6
 
7
+ static void rkadm5_config_cleanup(RUBY_KADM5_CONFIG *ptr){
8
+ if(!ptr->ctx)
9
+ return;
10
+
11
+ kadm5_free_config_params(ptr->ctx, &ptr->config);
12
+
13
+ if(ptr->rb_context == Qnil)
14
+ krb5_free_context(ptr->ctx);
15
+ else
16
+ rkrb5_context_release(ptr->rb_context);
17
+
18
+ ptr->ctx = NULL;
19
+ ptr->rb_context = Qnil;
20
+ memset(&ptr->config, 0, sizeof(ptr->config));
21
+ }
22
+
7
23
 
8
24
  // TypedData functions for RUBY_KADM5_CONFIG
25
+ static void rkadm5_config_typed_mark(void *ptr) {
26
+ if (!ptr) return;
27
+ RUBY_KADM5_CONFIG *c = (RUBY_KADM5_CONFIG *)ptr;
28
+ if (c->rb_context != Qnil)
29
+ rb_gc_mark(c->rb_context);
30
+ }
31
+
9
32
  static void rkadm5_config_typed_free(void *ptr) {
10
33
  if (!ptr) return;
11
34
  RUBY_KADM5_CONFIG *c = (RUBY_KADM5_CONFIG *)ptr;
12
- if (c->ctx) {
13
- kadm5_free_config_params(c->ctx, &c->config);
14
- krb5_free_context(c->ctx);
15
- }
35
+ rkadm5_config_cleanup(c);
16
36
  free(c);
17
37
  }
18
38
 
@@ -22,7 +42,7 @@ static size_t rkadm5_config_typed_size(const void *ptr) {
22
42
 
23
43
  const rb_data_type_t rkadm5_config_data_type = {
24
44
  "RUBY_KADM5_CONFIG",
25
- {NULL, rkadm5_config_typed_free, rkadm5_config_typed_size,},
45
+ {rkadm5_config_typed_mark, rkadm5_config_typed_free, rkadm5_config_typed_size,},
26
46
  NULL, NULL, RUBY_TYPED_FREE_IMMEDIATELY
27
47
  };
28
48
 
@@ -30,6 +50,7 @@ const rb_data_type_t rkadm5_config_data_type = {
30
50
  static VALUE rkadm5_config_allocate(VALUE klass){
31
51
  RUBY_KADM5_CONFIG* ptr = ALLOC(RUBY_KADM5_CONFIG);
32
52
  memset(ptr, 0, sizeof(RUBY_KADM5_CONFIG));
53
+ ptr->rb_context = Qnil;
33
54
  return TypedData_Wrap_Struct(klass, &rkadm5_config_data_type, ptr);
34
55
  }
35
56
 
@@ -43,23 +64,50 @@ static VALUE rkeysalt_new(krb5_enctype enctype, krb5_int32 salttype){
43
64
  }
44
65
 
45
66
  /*
67
+ * call-seq:
68
+ * Kerberos::Kadm5::Config.new(context: nil)
69
+ *
46
70
  * Returns a Kerberos::Kadm5::Config object. This object contains Kerberos
47
71
  * admin configuration.
48
72
  *
73
+ * Accepts the following keyword argument:
74
+ *
75
+ * - +context+: A Kerberos::Krb5::Context object. If provided, that context is
76
+ * used instead of creating a new one via krb5_init_context.
77
+ *
49
78
  * Note that the returned object is frozen. Changes made to the Kerberos
50
79
  * admin configuration options after the call will not be reflected in this
51
80
  * object.
52
81
  */
53
- static VALUE rkadm5_config_initialize(VALUE self){
82
+ static VALUE rkadm5_config_initialize(int argc, VALUE* argv, VALUE self){
54
83
  RUBY_KADM5_CONFIG* ptr;
55
84
  krb5_error_code kerror;
85
+ VALUE v_opts, v_context;
86
+ ID kw_table[1] = { rb_intern("context") };
87
+ VALUE kw_vals[1];
56
88
 
57
89
  TypedData_Get_Struct(self, RUBY_KADM5_CONFIG, &rkadm5_config_data_type, ptr);
58
90
 
59
- kerror = krb5_init_context(&ptr->ctx);
91
+ rb_scan_args(argc, argv, "0:", &v_opts);
60
92
 
61
- if(kerror)
62
- rb_raise(cKrb5Exception, "krb5_init_context: %s", error_message(kerror));
93
+ if(NIL_P(v_opts))
94
+ v_opts = rb_hash_new();
95
+
96
+ rb_get_kwargs(v_opts, kw_table, 0, 1, kw_vals);
97
+ v_context = kw_vals[0] == Qundef ? Qnil : kw_vals[0];
98
+
99
+ if(!NIL_P(v_context)){
100
+ ptr->ctx = rkrb5_context_borrow(v_context);
101
+ ptr->rb_context = v_context;
102
+ }
103
+ else{
104
+ kerror = krb5_init_context(&ptr->ctx);
105
+
106
+ if(kerror)
107
+ rb_raise(cKrb5Exception, "krb5_init_context: %s", error_message(kerror));
108
+
109
+ ptr->rb_context = Qnil;
110
+ }
63
111
 
64
112
  kerror = kadm5_get_config_params(
65
113
  ptr->ctx,
@@ -188,6 +236,10 @@ static VALUE rkadm5_config_initialize(VALUE self){
188
236
  rb_iv_set(self, "@keysalts", Qnil);
189
237
  }
190
238
 
239
+ // All configuration data has been copied into Ruby values, so this object
240
+ // no longer needs to retain its native context lease.
241
+ rkadm5_config_cleanup(ptr);
242
+
191
243
  // This is read only data
192
244
  rb_obj_freeze(self);
193
245
 
@@ -300,7 +352,7 @@ void Init_config(void){
300
352
 
301
353
  // Initializer
302
354
 
303
- rb_define_method(cKadm5Config, "initialize", rkadm5_config_initialize, 0);
355
+ rb_define_method(cKadm5Config, "initialize", rkadm5_config_initialize, -1);
304
356
 
305
357
  // Methods
306
358
 
@@ -15,6 +15,38 @@ static void rkrb5_context_typed_free(void *ptr) {
15
15
  free(c);
16
16
  }
17
17
 
18
+ krb5_context rkrb5_context_borrow(VALUE v_context){
19
+ RUBY_KRB5_CONTEXT* ptr;
20
+
21
+ if(!rb_obj_is_kind_of(v_context, cKrb5Context))
22
+ rb_raise(rb_eTypeError, "context must be a Kerberos::Krb5::Context object");
23
+
24
+ TypedData_Get_Struct(v_context, RUBY_KRB5_CONTEXT, &rkrb5_context_data_type, ptr);
25
+
26
+ if(!ptr->ctx || ptr->closed)
27
+ rb_raise(cKrb5Exception, "context is closed");
28
+
29
+ ptr->borrowers++;
30
+ return ptr->ctx;
31
+ }
32
+
33
+ void rkrb5_context_release(VALUE v_context){
34
+ RUBY_KRB5_CONTEXT* ptr;
35
+
36
+ if(NIL_P(v_context))
37
+ return;
38
+
39
+ ptr = (RUBY_KRB5_CONTEXT*)RTYPEDDATA_DATA(v_context);
40
+
41
+ if(ptr->borrowers > 0)
42
+ ptr->borrowers--;
43
+
44
+ if(ptr->closed && ptr->borrowers == 0 && ptr->ctx){
45
+ krb5_free_context(ptr->ctx);
46
+ ptr->ctx = NULL;
47
+ }
48
+ }
49
+
18
50
  static size_t rkrb5_context_typed_size(const void *ptr) {
19
51
  return sizeof(RUBY_KRB5_CONTEXT);
20
52
  }
@@ -34,30 +66,53 @@ static VALUE rkrb5_context_allocate(VALUE klass){
34
66
 
35
67
  /*
36
68
  * call-seq:
37
- * context.close
69
+ * context.close(force: false)
38
70
  *
39
- * Closes the context object.
71
+ * Closes the context object. Raises if wrappers are still borrowing the
72
+ * context. With +force: true+, the context is closed to new operations and
73
+ * borrowers immediately, but its native resources are retained until the
74
+ * existing borrowers have closed.
40
75
  */
41
- static VALUE rkrb5_context_close(VALUE self){
76
+ static VALUE rkrb5_context_close(int argc, VALUE* argv, VALUE self){
42
77
  RUBY_KRB5_CONTEXT* ptr;
78
+ VALUE v_opts, v_force;
79
+ ID kw_table[1] = { rb_intern("force") };
80
+ VALUE kw_vals[1];
43
81
 
44
82
  TypedData_Get_Struct(self, RUBY_KRB5_CONTEXT, &rkrb5_context_data_type, ptr);
45
83
 
46
- if(ptr->ctx)
47
- krb5_free_context(ptr->ctx);
84
+ rb_scan_args(argc, argv, "0:", &v_opts);
85
+
86
+ if(NIL_P(v_opts))
87
+ v_opts = rb_hash_new();
88
+
89
+ rb_get_kwargs(v_opts, kw_table, 0, 1, kw_vals);
90
+ v_force = kw_vals[0] == Qundef ? Qfalse : kw_vals[0];
48
91
 
49
- ptr->ctx = NULL;
92
+ if(ptr->closed || !ptr->ctx)
93
+ return self;
94
+
95
+ if(ptr->borrowers > 0 && !RTEST(v_force))
96
+ rb_raise(cKrb5Exception, "context is in use by %lu dependent wrapper%s",
97
+ (unsigned long)ptr->borrowers, ptr->borrowers == 1 ? "" : "s");
98
+
99
+ ptr->closed = 1;
100
+
101
+ if(ptr->borrowers == 0){
102
+ krb5_free_context(ptr->ctx);
103
+ ptr->ctx = NULL;
104
+ }
50
105
 
51
106
  return self;
52
107
  }
53
108
 
54
109
  /*
55
110
  * call-seq:
56
- * Kerberos::Context.new(options = {})
111
+ * Kerberos::Krb5::Context.new(secure: false, profile: nil)
57
112
  *
58
113
  * Creates and returns a new Kerberos::Context object.
59
114
  *
60
- * The options hash may be one or both of the following keys:
115
+ * The following keyword arguments are supported:
61
116
  *
62
117
  * :secure => true|false # Use config files only, ignore env variables
63
118
  * :profile => '/path/to/krb5.conf' # Use the specified profile file
@@ -68,11 +123,13 @@ static VALUE rkrb5_context_initialize(int argc, VALUE *argv, VALUE self){
68
123
  RUBY_KRB5_CONTEXT* ptr;
69
124
  VALUE v_opts;
70
125
  VALUE v_secure, v_profile;
126
+ ID kw_table[2] = { rb_intern("secure"), rb_intern("profile") };
127
+ VALUE kw_vals[2];
71
128
  krb5_error_code kerror;
72
129
 
73
130
  TypedData_Get_Struct(self, RUBY_KRB5_CONTEXT, &rkrb5_context_data_type, ptr);
74
131
 
75
- rb_scan_args(argc, argv, "01", &v_opts);
132
+ rb_scan_args(argc, argv, "0:", &v_opts);
76
133
 
77
134
  // Default behavior is a normal context that may respect environment.
78
135
  if (NIL_P(v_opts)) {
@@ -83,10 +140,9 @@ static VALUE rkrb5_context_initialize(int argc, VALUE *argv, VALUE self){
83
140
  return self;
84
141
  }
85
142
 
86
- Check_Type(v_opts, T_HASH);
87
-
88
- v_secure = rb_hash_aref2(v_opts, ID2SYM(rb_intern("secure")));
89
- v_profile = rb_hash_aref2(v_opts, ID2SYM(rb_intern("profile")));
143
+ rb_get_kwargs(v_opts, kw_table, 0, 2, kw_vals);
144
+ v_secure = kw_vals[0] == Qundef ? Qfalse : kw_vals[0];
145
+ v_profile = kw_vals[1] == Qundef ? Qnil : kw_vals[1];
90
146
 
91
147
  /*
92
148
  * If a profile path is supplied, load it via profile_init_path() and
@@ -133,6 +189,66 @@ static VALUE rkrb5_context_initialize(int argc, VALUE *argv, VALUE self){
133
189
  return self;
134
190
  }
135
191
 
192
+ /*
193
+ * call-seq:
194
+ * context.default_realm -> String
195
+ *
196
+ * Returns the default realm from this context.
197
+ */
198
+ static VALUE rkrb5_context_default_realm(VALUE self){
199
+ RUBY_KRB5_CONTEXT* ptr;
200
+ char* realm;
201
+ krb5_error_code kerror;
202
+
203
+ TypedData_Get_Struct(self, RUBY_KRB5_CONTEXT, &rkrb5_context_data_type, ptr);
204
+
205
+ if(!ptr->ctx || ptr->closed)
206
+ rb_raise(cKrb5Exception, "no context has been established");
207
+
208
+ kerror = krb5_get_default_realm(ptr->ctx, &realm);
209
+
210
+ if(kerror)
211
+ rb_raise(cKrb5Exception, "krb5_get_default_realm: %s", error_message(kerror));
212
+
213
+ VALUE v_realm = rb_str_new2(realm);
214
+ krb5_free_default_realm(ptr->ctx, realm);
215
+
216
+ return v_realm;
217
+ }
218
+
219
+ /*
220
+ * call-seq:
221
+ * context.default_realm = realm
222
+ *
223
+ * Sets the default realm for this context. If +realm+ is nil the default
224
+ * from krb5.conf is restored.
225
+ */
226
+ static VALUE rkrb5_context_set_default_realm(VALUE self, VALUE v_realm){
227
+ RUBY_KRB5_CONTEXT* ptr;
228
+ char* realm;
229
+ krb5_error_code kerror;
230
+
231
+ TypedData_Get_Struct(self, RUBY_KRB5_CONTEXT, &rkrb5_context_data_type, ptr);
232
+
233
+ if(!ptr->ctx || ptr->closed)
234
+ rb_raise(cKrb5Exception, "no context has been established");
235
+
236
+ if(NIL_P(v_realm)){
237
+ realm = NULL;
238
+ }
239
+ else{
240
+ Check_Type(v_realm, T_STRING);
241
+ realm = StringValueCStr(v_realm);
242
+ }
243
+
244
+ kerror = krb5_set_default_realm(ptr->ctx, realm);
245
+
246
+ if(kerror)
247
+ rb_raise(cKrb5Exception, "krb5_set_default_realm: %s", error_message(kerror));
248
+
249
+ return v_realm;
250
+ }
251
+
136
252
  void Init_context(void){
137
253
  /* The Kerberos::Krb5::Context class encapsulates a Kerberos context. */
138
254
  cKrb5Context = rb_define_class_under(cKrb5, "Context", rb_cObject);
@@ -144,5 +260,7 @@ void Init_context(void){
144
260
  rb_define_method(cKrb5Context, "initialize", rkrb5_context_initialize, -1);
145
261
 
146
262
  // Instance Methods
147
- rb_define_method(cKrb5Context, "close", rkrb5_context_close, 0);
263
+ rb_define_method(cKrb5Context, "close", rkrb5_context_close, -1);
264
+ rb_define_method(cKrb5Context, "default_realm", rkrb5_context_default_realm, 0);
265
+ rb_define_method(cKrb5Context, "default_realm=", rkrb5_context_set_default_realm, 1);
148
266
  }