ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-MCP/lib/AnyEvent/MCP/Config.pm
Revision: 1.33
Committed: Tue Nov 10 21:05:58 2026 UTC (3 days, 13 hours ago) by root
Branch: MAIN
CVS Tags: rel-0_91, HEAD
Changes since 1.32: +7 -5 lines
Log Message:
*** empty log message ***

File Contents

# User Rev Content
1 root 1.1 =head1 NAME
2    
3     AnyEvent::MCP::Config - named servers and derived MCP credentials
4    
5     =head1 SYNOPSIS
6    
7 root 1.12 use AnyEvent::MCP::Config;
8    
9 root 1.30 my $config = AnyEvent::MCP::Config::global_config;
10 root 1.24 my $token = $config->user_credentials ("agni")->[0][2];
11 root 1.6 my $url = $config->http_url ("agni", undef, path => "/");
12 root 1.1
13     =head1 DESCRIPTION
14    
15 root 1.3 An MCP server and its clients often need matching credentials. Rather than
16     maintaining a separate password for each server and user, this module can
17 root 1.24 derive them from one private master secret. Users are defined by
18     C<user.NAME = GENERATION> in C<[global]>, C<[program PROGRAM]> or
19     C<[instance NAME]>, in that order of precedence. An absent global
20     C<user.default> is implicitly enabled at generation 1.
21 root 1.3
22     The shared file format is described in L<AnyEvent::MCP/GLOBAL CONFIGURATION>.
23 root 1.30 Loading this module does not read any files. C<global_config> loads the
24     configuration, ensures a persistent master secret exists and caches the
25     initialized object for the process.
26    
27     A missing secret is generated using C<AnyEvent::MCP::random_bytes> and
28     appended under a file lock. Failure to save the secret is fatal, rather
29     than using a temporary secret that other processes cannot reproduce.
30     A supplied invalid secret is an error. Changing or losing the master
31     secret invalidates all credentials derived from it.
32 root 1.1
33 root 1.13 Derivation uses C<derive_secret> with the random master key.
34 root 1.14 The application namespace, purpose, program, instance name and username are
35     separately length-prefixed, so distinct contexts derive different credentials.
36 root 1.3 Deriving a password does not create an account or configure the server to
37     accept it: authentication still belongs to the server.
38 root 1.1
39     =head1 METHODS
40    
41 root 1.12 =over
42    
43 root 1.30 =item $config = AnyEvent::MCP::Config::global_config (file => $file)
44 root 1.1
45 root 1.30 Return the process-global configuration object, loaded and initialized.
46     The first call accepts the same arguments as C<new>, and ensures that the
47     master secret is persistently stored. Later calls return the same object
48     without rereading or reinitializing it.
49 root 1.1
50 root 1.12 =item $config = AnyEvent::MCP::Config->new (file => $file)
51 root 1.1
52 root 1.30 Construct an independent configuration object without reading or writing
53     the file. C<$file> defaults to the global configuration path.
54    
55     =item $config->load
56    
57     Reload the file, replacing the object's sections. A missing file yields an
58     empty configuration; other read and parsing errors are fatal. Sections are
59     available as C<< $config->{section}{$section} >>, each a hash reference of
60     string values. Named servers use C<[instance NAME]>. An optional raw file
61     handle can be supplied when the caller already holds the file lock.
62    
63 root 1.32 =item $config->save
64    
65     Write the loaded sections back as canonical UTF-8 INI text, sorting
66     sections and keys. Comments and original formatting are not retained.
67     The caller must hold C<AnyEvent::MCP::file_lock> through the update.
68     Returns the configuration object.
69    
70 root 1.30 =item $config->ensure_secret
71    
72     Ensure that C<[global]> contains a valid persistent master secret. If
73     missing, lock and reread the file, generate a secret only if still absent,
74     and append it without changing other settings. Fail if it cannot be saved
75     or is invalid. Returns the configuration object.
76 root 1.1
77 root 1.13 =item $bytes = $config->derive_secret ($length, @info)
78    
79     =item $bytes = AnyEvent::MCP::Config->derive_secret ($length, @info)
80    
81     Derive C<$length> secret bytes from the configuration's master secret and
82     an ordered list of context byte strings C<@info>. The class form uses
83 root 1.30 C<global_config>, loading the default configuration on first use and reusing it
84 root 1.13 thereafter. The object form uses that object's configuration, including
85     one loaded from an explicit file.
86    
87     The same master secret and context always produce the same bytes. Use a
88     distinctive application/purpose string as the first context field, followed
89     by names or other values identifying the result:
90    
91     my $secret = AnyEvent::MCP::Config->derive_secret (
92     32, "my application token v1", AnyEvent::MCP::utf8enc ($name)
93     );
94    
95     The result contains raw bytes; encode it yourself if a printable token is
96     needed. This method does not add the result to a server's accepted tokens.
97    
98 root 1.14 The fixed C<anyevent-mcp> context field is prepended automatically.
99 root 1.13 Context fields are separately length-prefixed, so C<("ab", "c")> and
100     C<("a", "bc")> have different contexts. Encode character strings explicitly,
101     for example with C<AnyEvent::MCP::utf8enc>. Context arguments are left
102     unchanged.
103    
104     C<$length> must be an integer in 0..16320; zero returns an empty string.
105     Shorter results are prefixes of longer results for the same master secret
106     and context. Use distinct context fields for different purposes, rather
107     than relying on different lengths to separate them.
108    
109     Derivation uses HKDF-SHA512 expansion, with each context field prefixed by
110     its four-byte big-endian byte length. Changing or losing the master secret
111     changes every result derived from it.
112    
113 root 1.12 =item $credential = $config->credential ($purpose, $name, $username)
114 root 1.1
115     Derives a 32-byte credential, returned as unpadded URL-safe Base64.
116 root 1.14 C<$purpose> is C<token>. C<$name> is the instance name, and C<$username>
117 root 1.24 is optional. This is the older unmanaged derivation API; its result is not
118     automatically accepted by a server. Managed credentials are enumerated by
119     C<user_credentials>.
120 root 1.1
121 root 1.14 Credentials use the context C<("anyevent-mcp", "token", PROGRAM, NAME, USER)>.
122     PROGRAM is the invoked basename, with a trailing C<.pl> or C<.perl> removed.
123 root 1.18 Changing the program or instance name changes the credential.
124 root 1.12 A credential derived with C<$username> is B<not> automatically accepted
125     by the server. This method neither requires a server section nor configures
126     authentication.
127 root 1.1
128 root 1.24 =item $users = $config->users ($instance, $program)
129    
130     Return effective user definitions keyed by username, each with C<scope>
131     (global, program or instance) and signed C<generation>. Inheritance is
132     global, program, instance; a negative generation disables without fallback.
133    
134     =item $credentials = $config->user_credentials ($instance, $program)
135    
136     Return enabled users as C<[[USERNAME, PRINCIPAL, TOKEN]...]> with default
137     first. The principal is C<global:NAME>, C<program:NAME>, or C<instance:NAME>.
138     HKDF context includes program, instance, scope, user and generation. Rotating
139     a generation changes the token but preserves saved user permissions.
140    
141     =item $user = $config->connect_user ($instance, $program)
142    
143     Return the username allowed to register private Vim, browser WebSocket, or
144     multibroker reverse-MCP connections. C<connect_user> inherits through the
145     usual scopes and defaults to C<default>. Revoking that user disables these
146     connections until its generation is renewed or another user is selected.
147    
148 root 1.14 =item $program = AnyEvent::MCP::Config::program_name ()
149    
150     Returns the invoked program basename, stripping C<.pl> or C<.perl>.
151     An inline Perl invocation uses C<perl>. This identity also selects the
152     C<[program PROGRAM]> configuration section; it is independent of routing aliases.
153    
154 root 1.26 =item $user = AnyEvent::MCP::Config::user_name ($user)
155 root 1.4
156 root 1.26 Validate a managed username (ASCII letter or underscore, followed by
157     letters, digits, underscores or hyphens). Instance names are validated by
158     L<AnyEvent::MCP/SMALL UTILITIES>.
159 root 1.4
160 root 1.12 =item @options = AnyEvent::MCP::Config::http_getopt (\$name, \$bind, \$print_url, \@tokens)
161 root 1.4
162     Return standard C<Getopt::Long> options referring to the caller's scalars.
163    
164 root 1.12 =item $port = $config->server_port ($name)
165 root 1.4
166     Derive a stable default port in 16384..32767 from the master secret and
167 root 1.14 program basename and service-instance name. Collisions produce ordinary listener bind errors.
168 root 1.4
169 root 1.33 =item %settings = $config->http_settings ($name, $bind, $default_port)
170 root 1.4
171 root 1.10 Resolve HTTP settings from C<[instance NAME]> and an optional command-line
172 root 1.33 C<bind> override. The default is IPv4 loopback and the name-derived port;
173     an optional C<default_port> replaces the derived port when no bind port
174     is specified.
175 root 1.4 The bind setting accepts C<ADDRESS:PORT>, C<[IPv6]:PORT>, C<ADDRESS>,
176     and C<:PORT> (which keeps the default loopback address).
177 root 1.12 Hostnames are resolved once when settings are obtained; their original
178     spelling is advertised unless C<url_host> overrides it. The returned options
179     include C<name>, C<host>, C<port> and C<tokens>, plus configured C<path>,
180     C<url_host> and C<url_scheme>. This does not start a listener.
181 root 1.4
182 root 1.17 =item @rules = $config->auto_rules ($name)
183    
184     Return ordered automatic permission rules from C<[program PROGRAM]> and
185     C<[instance NAME]>. C<auto-rule.SUFFIX = allow|deny TOOL[,ACTION[,CAPABILITY]]>
186     entries are sorted by key. The suffix is an arbitrary ordering label; instance
187     entries override matching program entries, and an empty value disables an
188     inherited entry. Command-line rules take precedence over these defaults.
189    
190 root 1.12 =item $url = $config->http_url ($name, $bind, %options)
191 root 1.4
192 root 1.8 Format a URL containing the first accepted token, without listening.
193     Port zero cannot be used because the kernel has not yet selected a port.
194     When the token is derived, the master secret must be persistently stored,
195     so the next process derives the same URL. Explicit static tokens and
196     C<auth =E<gt> "none"> do not require that persistence check.
197 root 1.4
198 root 1.12 =back
199    
200 root 1.32 =head1 INI FUNCTIONS
201    
202     These functions work with Unicode character strings, not octets. Use
203     L<AnyEvent::MCP/file_load> and L<AnyEvent::MCP/file_save> for UTF-8
204     file I/O. Binary values belong in Base64 or hexadecimal form.
205    
206     =over
207    
208     =item $sections = AnyEvent::MCP::Config::parse_ini ($text, $filename)
209    
210     Parse Unicode INI text into C<[[NAME, {KEY =E<gt> VALUE}], ...]>.
211     Sections retain file order, including repeated section names and the
212     initial unnamed section C<"">. Duplicate keys within one section use
213     the final value. C<$filename> is optional, for line-numbered errors.
214    
215     =item $merged = AnyEvent::MCP::Config::merge_sections ($sections, $default)
216    
217     Return a hash mapping section names to key/value hashes, with later
218     sections overriding earlier ones. When C<$default> is given, unnamed
219     sections use that name (C<global> for global configuration and
220     C<account> for IMAP accounts). Use the original ordered list instead
221     for files where repeated sections are meaningful, such as permission rules.
222    
223     =item $text = AnyEvent::MCP::Config::serialize_ini ($sections)
224    
225     Serialize an ordered section list to canonical INI text. Section order
226     is preserved; keys within each section are sorted. Values must be
227     single-line character strings. Comments and whitespace formatting
228     are deliberately not preserved on rewrite.
229    
230     =back
231    
232 root 1.1 =cut
233    
234     package AnyEvent::MCP::Config;
235    
236     use common::sense;
237 root 1.25
238 root 1.13 use Digest::SHA ();
239 root 1.1 use MIME::Base64 ();
240     use File::Basename ();
241     use Errno qw(ENOENT);
242 root 1.25
243 root 1.4 use AnyEvent::Socket ();
244 root 1.25 use AnyEvent::MCP ();
245 root 1.1
246     our @ISA = qw(AnyEvent::MCP::Base);
247    
248 root 1.32 sub parse_ini {
249     my ($text, $file) = @_;
250     $file //= "INI";
251     my @sections = (["", {}]);
252     my $line = 0;
253    
254     for (split /\n/, $text) {
255     ++$line;
256     s/\r\z//;
257     next if /\A\s*(?:[#;]|\z)/;
258     if (/\A\s*\[([^\]\r\n]+)\]\s*\z/) {
259     push @sections, [$1, {}];
260     } elsif (/\A\s*([^=\s]+)\s*=\s*(.*?)\s*\z/) {
261     $sections[-1][1]{$1} = $2;
262     } else {
263     die "$file:$line: invalid INI line\n";
264     }
265     }
266    
267     \@sections
268     }
269    
270     sub merge_sections {
271     my ($sections, $default) = @_;
272     my %merged;
273    
274     for (@$sections) {
275     my ($name, $kv) = @$_;
276     $name = $default if defined $default && $name eq "";
277     my $values = $merged{$name} ||= {};
278     %$values = (%$values, %$kv);
279     }
280    
281     \%merged
282     }
283    
284     sub serialize_ini {
285     my ($sections) = @_;
286    
287     join "", map {
288     my ($name, $values) = @$_;
289     $name !~ /[\]\r\n]/ or die "Invalid INI section name\n";
290     (length $name ? "[$name]\n" : "")
291     . (join "", map {
292     length && !/[=\s]/ && !/\A[#;]/
293     or die "Invalid INI key\n";
294     my $value = $values->{$_};
295     defined $value && !ref $value && $value !~ /[\r\n]/
296     or die "Invalid INI value for $_\n";
297     "$_ = $value\n"
298     } sort keys %$values)
299     . "\n"
300     } @$sections
301     }
302    
303 root 1.1 sub _file {
304     my $dir = $ENV{XDG_CONFIG_HOME}
305     || ($ENV{HOME} || die "HOME is not set\n") . "/.config";
306    
307     "$dir/anyevent-mcp/global.ini"
308     }
309    
310     my $global;
311    
312 root 1.30 sub global_config {
313 root 1.31 $global //= __PACKAGE__->new (@_)->load->ensure_secret
314 root 1.1 }
315    
316 root 1.16 sub new {
317 root 1.1 my ($class, %kv) = @_;
318 root 1.11
319 root 1.1 my $self = $class->SUPER::new (section => {}, %kv);
320     $self->{file} = _file unless exists $self->{file};
321 root 1.30 $self
322     }
323 root 1.1
324 root 1.30 sub load {
325     my ($self, $fh) = @_;
326     $self->{section} = {};
327    
328     unless ($fh) {
329     open $fh, "<:raw", $self->{file} or do {
330     die "$self->{file}: $!\n" unless $! == ENOENT;
331     return $self
332     };
333 root 1.1 }
334    
335 root 1.30 seek $fh, 0, 0 or die "$self->{file}: $!\n";
336 root 1.32 $self->{section} = merge_sections (parse_ini (AnyEvent::MCP::file_load ($self->{file}, $fh), $self->{file}), "global");
337 root 1.30 close $fh or die "$self->{file}: $!\n" unless @_ > 1;
338 root 1.1
339     $self
340     }
341    
342 root 1.32 sub save {
343     my ($self) = @_;
344 root 1.1
345 root 1.32 AnyEvent::MCP::file_save $self->{file},
346     serialize_ini [map [$_, $self->{section}{$_}], sort keys %{$self->{section}}];
347     $self
348 root 1.1 }
349    
350 root 1.30 sub ensure_secret {
351 root 1.1 my ($self) = @_;
352    
353 root 1.30 unless (exists $self->{section}{global}{secret}) {
354     my $lock = AnyEvent::MCP::file_lock $self->{file};
355     $self->load ($lock);
356    
357     unless (exists $self->{section}{global}{secret}) {
358     my $secret = eval { unpack "H*", AnyEvent::MCP::random_bytes 64 };
359     die "$@Supply a securely generated 128-digit hexadecimal secret in $self->{file}\n"
360     if $@;
361     seek $lock, 0, 2 or die "$self->{file}: $!\n";
362     print {$lock} "\n[global]\nsecret = $secret\n" or die "$self->{file}: $!\n";
363     $self->{section}{global}{secret} = $secret;
364 root 1.1 }
365    
366 root 1.30 close $lock or die "$self->{file}: $!\n";
367     }
368 root 1.1
369 root 1.30 $self->{section}{global}{secret} =~ /\A[0-9a-fA-F]{128}\z/
370     or die "$self->{file}: invalid secret; supply 128 hexadecimal digits\n";
371     $self
372 root 1.1 }
373    
374 root 1.13 sub _prk_derive_secret($$@) {
375     my ($prk, $length, @info) = @_;
376    
377     64 == length $prk
378     or die "derive_secret: key length must be 64 octets\n";
379     $length =~ /\A[0-9]+\z/ && $length <= 255 * 64
380     or die "derive_secret: length must be an integer in 0..16320\n";
381    
382 root 1.14 my $info = pack "(N/a*)*", "anyevent-mcp", @info;
383 root 1.13 my $t = "";
384     my $okm = "";
385     my $i = 0;
386    
387     while ($length > length $okm) {
388     $t = Digest::SHA::hmac_sha512 $t . $info . chr ++$i, $prk;
389     $okm .= $t;
390     }
391    
392     substr $okm, 0, $length
393     }
394    
395     sub derive_secret {
396     my ($self, $length, @info) = @_;
397    
398 root 1.30 $self = global_config () unless ref $self;
399 root 1.13
400     my $secret = $self->{section}{global}{secret};
401     $secret =~ /\A[0-9a-fA-F]{128}\z/
402     or die "$self->{file}: missing or invalid secret\n";
403    
404     _prk_derive_secret (pack ("H*", $secret), $length, @info)
405     }
406    
407 root 1.21 sub program_name {
408     my $name = File::Basename::basename ($0);
409     $name =~ s/\.(?:pl|perl)\z//;
410     $name eq "-e" ? "perl" : $name
411     }
412    
413 root 1.26 sub user_name {
414 root 1.21 my ($name) = @_;
415    
416 root 1.26 defined $name && $name =~ /\A[A-Za-z_][A-Za-z0-9_-]*\z/
417     or die "Invalid MCP user name\n";
418 root 1.21
419     $name
420     }
421    
422 root 1.1 sub credential {
423     my ($self, $purpose, $name, $username) = @_;
424    
425     $username = "" unless defined $username;
426    
427 root 1.11 $purpose eq "token"
428     or die "Unknown credential purpose '$purpose'\n";
429 root 1.1
430     my $credential = MIME::Base64::encode_base64 (
431 root 1.13 $self->derive_secret (
432 root 1.21 32,
433     $purpose,
434     (AnyEvent::MCP::utf8enc program_name),
435     (AnyEvent::MCP::utf8enc $name),
436     (AnyEvent::MCP::utf8enc $username)
437 root 1.13 ), ""
438 root 1.1 );
439     $credential =~ y!+/=!-_!d;
440    
441     $credential
442     }
443    
444 root 1.24 sub users {
445     my ($self, $name, $program) = @_;
446    
447 root 1.26 $name = AnyEvent::MCP::instance_name $name;
448 root 1.24 $program //= program_name;
449     my %users = (default => { scope => "global", generation => 1 });
450    
451     for my $entry (
452     ["global", $self->{section}{global}],
453     ["program", $self->{section}{"program $program"}],
454     ["instance", $self->{section}{"instance $name"}],
455     ) {
456     my ($scope, $section) = @$entry;
457     next unless $section;
458    
459     for my $key (grep /\Auser\./, keys %$section) {
460     my ($user) = $key =~ /\Auser\.([A-Za-z_][A-Za-z0-9_-]*)\z/
461     or die "Invalid MCP user '$key'\n";
462     my $generation = $section->{$key};
463     $generation =~ /\A-?[1-9][0-9]*\z/ && length $generation < 18
464     or die "Invalid MCP user generation for '$key'\n";
465     $users{$user} = { scope => $scope, generation => $generation };
466     }
467     }
468    
469     \%users
470     }
471    
472     sub user_credentials {
473     my ($self, $name, $program) = @_;
474    
475 root 1.26 $name = AnyEvent::MCP::instance_name $name;
476 root 1.24 $program //= program_name;
477     my $users = $self->users ($name, $program);
478     my @credentials;
479    
480     for my $user (sort { ($a ne "default") <=> ($b ne "default") || $a cmp $b } keys %$users) {
481     my $info = $users->{$user};
482     next unless $info->{generation} > 0;
483     my $token = MIME::Base64::encode_base64 ($self->derive_secret (
484     32, "user-token-v1", AnyEvent::MCP::utf8enc ($program),
485     AnyEvent::MCP::utf8enc ($name), $info->{scope},
486     AnyEvent::MCP::utf8enc ($user), "$info->{generation}"
487     ), "");
488     $token =~ y!+/=!-_!d;
489     push @credentials, [$user, "$info->{scope}:$user", $token];
490     }
491    
492     \@credentials
493     }
494    
495     sub connect_user {
496     my ($self, $name, $program) = @_;
497    
498 root 1.26 $name = AnyEvent::MCP::instance_name $name;
499 root 1.24 $program //= program_name;
500     my $user = $self->{section}{"instance $name"}{connect_user}
501     // $self->{section}{"program $program"}{connect_user}
502     // $self->{section}{global}{connect_user} // "default";
503 root 1.26 user_name $user
504 root 1.24 }
505    
506 root 1.4 sub http_getopt {
507 root 1.5 my ($name, $bind, $print_url, $tokens) = @_;
508 root 1.4
509 root 1.24 "name=s" => $name, "bind=s" => $bind, "print-url|print-urls:s" => $print_url, "token=s@" => $tokens
510 root 1.4 }
511    
512 root 1.17 sub auto_rules {
513     my ($self, $name) = @_;
514     my %settings = (
515     %{$self->{section}{"program " . program_name ()} || {}},
516 root 1.26 %{$self->{section}{"instance " . AnyEvent::MCP::instance_name ($name)} || {}},
517 root 1.17 );
518     my @rules;
519    
520     for my $key (sort grep /\Aauto-rule\./, keys %settings) {
521     $key =~ /\Aauto-rule\.(.+)\z/
522     or die "Invalid automatic permission rule key '$key'\n";
523     my $value = $settings{$key};
524     next unless length $value;
525     my ($decision, $spec) = $value =~ /\A(allow|deny)\s+(\S+)\z/
526     or die "Invalid configured automatic permission rule '$key'\n";
527     push @rules, [$decision, $spec];
528     }
529    
530     @rules
531     }
532    
533 root 1.4 sub server_port {
534     my ($self, $name) = @_;
535    
536 root 1.26 $name = AnyEvent::MCP::instance_name $name;
537 root 1.4
538 root 1.21 16384 + (
539     (unpack "n", $self->derive_secret (
540     2,
541     "port",
542     (AnyEvent::MCP::utf8enc program_name),
543     (AnyEvent::MCP::utf8enc $name))
544     ) & 16383)
545 root 1.4 }
546    
547     sub http_settings {
548 root 1.33 my ($self, $name, $bind, $default_port) = @_;
549 root 1.4
550 root 1.26 $name = AnyEvent::MCP::instance_name $name;
551 root 1.14 my $settings = {
552     %{ $self->{section}{"program " . program_name ()} },
553     %{ $self->{section}{"instance $name"} },
554     };
555 root 1.33 my $port = $default_port // $self->server_port ($name);
556 root 1.4 my $configured = $settings->{bind} // "127.0.0.1:$port";
557     $configured = "127.0.0.1$configured" if $configured =~ /\A:[0-9]+\z/;
558     my ($configured_host, $configured_port) = AnyEvent::Socket::parse_hostport $configured, $port;
559     defined $configured_host && defined $configured_port
560     && $configured_port =~ /\A[0-9]+\z/ && $configured_port <= 65535
561     or die "Invalid MCP bind address '$configured'\n";
562    
563     $bind = $configured unless defined $bind;
564     $bind = "127.0.0.1$bind" if $bind =~ /\A:[0-9]+\z/;
565    
566     my ($host, $service) = AnyEvent::Socket::parse_hostport $bind, $configured_port;
567     defined $host && defined $service && $service =~ /\A[0-9]+\z/ && $service <= 65535
568     or die "Invalid MCP bind address '$bind'\n";
569    
570     my %kv = (
571     (map { exists $settings->{$_} ? ($_ => $settings->{$_}) : () }
572 root 1.23 qw(path url_host url_scheme tls_cert tls_key)),
573 root 1.4 name => $name,
574     host => $host,
575     port => $service,
576     );
577    
578     unless (AnyEvent::Socket::parse_address $host) {
579 root 1.20 my $cv = AnyEvent->condvar;
580     AnyEvent::Socket::resolve_sockaddr $host, $service, "tcp", 0, 0, $cv;
581     my ($resolved) = $cv->recv;
582     $resolved or die "Cannot resolve MCP bind host '$host'\n";
583 root 1.4
584 root 1.20 my (undef, $address) = AnyEvent::Socket::unpack_sockaddr $resolved->[3];
585 root 1.4 $kv{url_host} //= $host;
586 root 1.9 $kv{host} = AnyEvent::Socket::format_address $address;
587 root 1.4 }
588    
589 root 1.24 if (exists $settings->{tokens}) {
590     $kv{tokens} = [split /\s*,\s*/, $settings->{tokens}, -1];
591     $kv{connect_tokens} = $kv{tokens};
592     } else {
593     my $credentials = $self->user_credentials ($name);
594     $kv{tokens} = [map $_->[2], @$credentials];
595     $kv{token_users} = { map { $_->[2] => $_->[1] } @$credentials };
596     my $connect = $self->connect_user ($name);
597     $kv{connect_tokens} = [map $_->[2], grep $_->[0] eq $connect, @$credentials];
598     }
599 root 1.4
600     %kv
601     }
602    
603     sub http_url {
604     my ($self, $name, $bind, %options) = @_;
605    
606 root 1.26 $name = AnyEvent::MCP::instance_name $name;
607 root 1.14
608 root 1.11 # only a derived token depends on the master secret surviving this process.
609 root 1.8 if (($options{auth} || "") ne "none"
610     && !exists $options{tokens}
611 root 1.14 && !exists $self->{section}{"instance $name"}{tokens}
612 root 1.24 && !exists $self->{section}{"program " . program_name ()}{tokens}
613     && !exists $self->{section}{global}{tokens}) {
614 root 1.30 my $stored = (ref $self)->new (file => $self->{file})->load;
615 root 1.8 $stored->{section}{global}{secret} eq $self->{section}{global}{secret}
616     or die "MCP master secret is not persistently stored in $self->{file}\n";
617     }
618 root 1.4
619     require AnyEvent::MCP::Server::HTTP;
620 root 1.33 my %http = $self->http_settings ($name, $bind, delete $options{default_port});
621 root 1.8 delete $http{tokens} if ($options{auth} || "") eq "none";
622 root 1.11
623     AnyEvent::MCP::Server::HTTP->new (%http, %options)->url
624 root 1.4 }
625    
626 root 1.1
627     1