ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-MCP/lib/AnyEvent/MCP/Config.pm
Revision: 1.32
Committed: Sun Nov 8 02:52:58 2026 UTC (15 hours, 53 minutes ago) by root
Branch: MAIN
CVS Tags: HEAD
Changes since 1.31: +100 -8 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.12 =item %settings = $config->http_settings ($name, $bind)
170 root 1.4
171 root 1.10 Resolve HTTP settings from C<[instance NAME]> and an optional command-line
172 root 1.4 C<bind> override. The default is IPv4 loopback and the name-derived port.
173     The bind setting accepts C<ADDRESS:PORT>, C<[IPv6]:PORT>, C<ADDRESS>,
174     and C<:PORT> (which keeps the default loopback address).
175 root 1.12 Hostnames are resolved once when settings are obtained; their original
176     spelling is advertised unless C<url_host> overrides it. The returned options
177     include C<name>, C<host>, C<port> and C<tokens>, plus configured C<path>,
178     C<url_host> and C<url_scheme>. This does not start a listener.
179 root 1.4
180 root 1.17 =item @rules = $config->auto_rules ($name)
181    
182     Return ordered automatic permission rules from C<[program PROGRAM]> and
183     C<[instance NAME]>. C<auto-rule.SUFFIX = allow|deny TOOL[,ACTION[,CAPABILITY]]>
184     entries are sorted by key. The suffix is an arbitrary ordering label; instance
185     entries override matching program entries, and an empty value disables an
186     inherited entry. Command-line rules take precedence over these defaults.
187    
188 root 1.12 =item $url = $config->http_url ($name, $bind, %options)
189 root 1.4
190 root 1.8 Format a URL containing the first accepted token, without listening.
191     Port zero cannot be used because the kernel has not yet selected a port.
192     When the token is derived, the master secret must be persistently stored,
193     so the next process derives the same URL. Explicit static tokens and
194     C<auth =E<gt> "none"> do not require that persistence check.
195 root 1.4
196 root 1.12 =back
197    
198 root 1.32 =head1 INI FUNCTIONS
199    
200     These functions work with Unicode character strings, not octets. Use
201     L<AnyEvent::MCP/file_load> and L<AnyEvent::MCP/file_save> for UTF-8
202     file I/O. Binary values belong in Base64 or hexadecimal form.
203    
204     =over
205    
206     =item $sections = AnyEvent::MCP::Config::parse_ini ($text, $filename)
207    
208     Parse Unicode INI text into C<[[NAME, {KEY =E<gt> VALUE}], ...]>.
209     Sections retain file order, including repeated section names and the
210     initial unnamed section C<"">. Duplicate keys within one section use
211     the final value. C<$filename> is optional, for line-numbered errors.
212    
213     =item $merged = AnyEvent::MCP::Config::merge_sections ($sections, $default)
214    
215     Return a hash mapping section names to key/value hashes, with later
216     sections overriding earlier ones. When C<$default> is given, unnamed
217     sections use that name (C<global> for global configuration and
218     C<account> for IMAP accounts). Use the original ordered list instead
219     for files where repeated sections are meaningful, such as permission rules.
220    
221     =item $text = AnyEvent::MCP::Config::serialize_ini ($sections)
222    
223     Serialize an ordered section list to canonical INI text. Section order
224     is preserved; keys within each section are sorted. Values must be
225     single-line character strings. Comments and whitespace formatting
226     are deliberately not preserved on rewrite.
227    
228     =back
229    
230 root 1.1 =cut
231    
232     package AnyEvent::MCP::Config;
233    
234     use common::sense;
235 root 1.25
236 root 1.13 use Digest::SHA ();
237 root 1.1 use MIME::Base64 ();
238     use File::Basename ();
239     use Errno qw(ENOENT);
240 root 1.25
241 root 1.4 use AnyEvent::Socket ();
242 root 1.25 use AnyEvent::MCP ();
243 root 1.1
244     our @ISA = qw(AnyEvent::MCP::Base);
245    
246 root 1.32 sub parse_ini {
247     my ($text, $file) = @_;
248     $file //= "INI";
249     my @sections = (["", {}]);
250     my $line = 0;
251    
252     for (split /\n/, $text) {
253     ++$line;
254     s/\r\z//;
255     next if /\A\s*(?:[#;]|\z)/;
256     if (/\A\s*\[([^\]\r\n]+)\]\s*\z/) {
257     push @sections, [$1, {}];
258     } elsif (/\A\s*([^=\s]+)\s*=\s*(.*?)\s*\z/) {
259     $sections[-1][1]{$1} = $2;
260     } else {
261     die "$file:$line: invalid INI line\n";
262     }
263     }
264    
265     \@sections
266     }
267    
268     sub merge_sections {
269     my ($sections, $default) = @_;
270     my %merged;
271    
272     for (@$sections) {
273     my ($name, $kv) = @$_;
274     $name = $default if defined $default && $name eq "";
275     my $values = $merged{$name} ||= {};
276     %$values = (%$values, %$kv);
277     }
278    
279     \%merged
280     }
281    
282     sub serialize_ini {
283     my ($sections) = @_;
284    
285     join "", map {
286     my ($name, $values) = @$_;
287     $name !~ /[\]\r\n]/ or die "Invalid INI section name\n";
288     (length $name ? "[$name]\n" : "")
289     . (join "", map {
290     length && !/[=\s]/ && !/\A[#;]/
291     or die "Invalid INI key\n";
292     my $value = $values->{$_};
293     defined $value && !ref $value && $value !~ /[\r\n]/
294     or die "Invalid INI value for $_\n";
295     "$_ = $value\n"
296     } sort keys %$values)
297     . "\n"
298     } @$sections
299     }
300    
301 root 1.1 sub _file {
302     my $dir = $ENV{XDG_CONFIG_HOME}
303     || ($ENV{HOME} || die "HOME is not set\n") . "/.config";
304    
305     "$dir/anyevent-mcp/global.ini"
306     }
307    
308     my $global;
309    
310 root 1.30 sub global_config {
311 root 1.31 $global //= __PACKAGE__->new (@_)->load->ensure_secret
312 root 1.1 }
313    
314 root 1.16 sub new {
315 root 1.1 my ($class, %kv) = @_;
316 root 1.11
317 root 1.1 my $self = $class->SUPER::new (section => {}, %kv);
318     $self->{file} = _file unless exists $self->{file};
319 root 1.30 $self
320     }
321 root 1.1
322 root 1.30 sub load {
323     my ($self, $fh) = @_;
324     $self->{section} = {};
325    
326     unless ($fh) {
327     open $fh, "<:raw", $self->{file} or do {
328     die "$self->{file}: $!\n" unless $! == ENOENT;
329     return $self
330     };
331 root 1.1 }
332    
333 root 1.30 seek $fh, 0, 0 or die "$self->{file}: $!\n";
334 root 1.32 $self->{section} = merge_sections (parse_ini (AnyEvent::MCP::file_load ($self->{file}, $fh), $self->{file}), "global");
335 root 1.30 close $fh or die "$self->{file}: $!\n" unless @_ > 1;
336 root 1.1
337     $self
338     }
339    
340 root 1.32 sub save {
341     my ($self) = @_;
342 root 1.1
343 root 1.32 AnyEvent::MCP::file_save $self->{file},
344     serialize_ini [map [$_, $self->{section}{$_}], sort keys %{$self->{section}}];
345     $self
346 root 1.1 }
347    
348 root 1.30 sub ensure_secret {
349 root 1.1 my ($self) = @_;
350    
351 root 1.30 unless (exists $self->{section}{global}{secret}) {
352     my $lock = AnyEvent::MCP::file_lock $self->{file};
353     $self->load ($lock);
354    
355     unless (exists $self->{section}{global}{secret}) {
356     my $secret = eval { unpack "H*", AnyEvent::MCP::random_bytes 64 };
357     die "$@Supply a securely generated 128-digit hexadecimal secret in $self->{file}\n"
358     if $@;
359     seek $lock, 0, 2 or die "$self->{file}: $!\n";
360     print {$lock} "\n[global]\nsecret = $secret\n" or die "$self->{file}: $!\n";
361     $self->{section}{global}{secret} = $secret;
362 root 1.1 }
363    
364 root 1.30 close $lock or die "$self->{file}: $!\n";
365     }
366 root 1.1
367 root 1.30 $self->{section}{global}{secret} =~ /\A[0-9a-fA-F]{128}\z/
368     or die "$self->{file}: invalid secret; supply 128 hexadecimal digits\n";
369     $self
370 root 1.1 }
371    
372 root 1.13 sub _prk_derive_secret($$@) {
373     my ($prk, $length, @info) = @_;
374    
375     64 == length $prk
376     or die "derive_secret: key length must be 64 octets\n";
377     $length =~ /\A[0-9]+\z/ && $length <= 255 * 64
378     or die "derive_secret: length must be an integer in 0..16320\n";
379    
380 root 1.14 my $info = pack "(N/a*)*", "anyevent-mcp", @info;
381 root 1.13 my $t = "";
382     my $okm = "";
383     my $i = 0;
384    
385     while ($length > length $okm) {
386     $t = Digest::SHA::hmac_sha512 $t . $info . chr ++$i, $prk;
387     $okm .= $t;
388     }
389    
390     substr $okm, 0, $length
391     }
392    
393     sub derive_secret {
394     my ($self, $length, @info) = @_;
395    
396 root 1.30 $self = global_config () unless ref $self;
397 root 1.13
398     my $secret = $self->{section}{global}{secret};
399     $secret =~ /\A[0-9a-fA-F]{128}\z/
400     or die "$self->{file}: missing or invalid secret\n";
401    
402     _prk_derive_secret (pack ("H*", $secret), $length, @info)
403     }
404    
405 root 1.21 sub program_name {
406     my $name = File::Basename::basename ($0);
407     $name =~ s/\.(?:pl|perl)\z//;
408     $name eq "-e" ? "perl" : $name
409     }
410    
411 root 1.26 sub user_name {
412 root 1.21 my ($name) = @_;
413    
414 root 1.26 defined $name && $name =~ /\A[A-Za-z_][A-Za-z0-9_-]*\z/
415     or die "Invalid MCP user name\n";
416 root 1.21
417     $name
418     }
419    
420 root 1.1 sub credential {
421     my ($self, $purpose, $name, $username) = @_;
422    
423     $username = "" unless defined $username;
424    
425 root 1.11 $purpose eq "token"
426     or die "Unknown credential purpose '$purpose'\n";
427 root 1.1
428     my $credential = MIME::Base64::encode_base64 (
429 root 1.13 $self->derive_secret (
430 root 1.21 32,
431     $purpose,
432     (AnyEvent::MCP::utf8enc program_name),
433     (AnyEvent::MCP::utf8enc $name),
434     (AnyEvent::MCP::utf8enc $username)
435 root 1.13 ), ""
436 root 1.1 );
437     $credential =~ y!+/=!-_!d;
438    
439     $credential
440     }
441    
442 root 1.24 sub users {
443     my ($self, $name, $program) = @_;
444    
445 root 1.26 $name = AnyEvent::MCP::instance_name $name;
446 root 1.24 $program //= program_name;
447     my %users = (default => { scope => "global", generation => 1 });
448    
449     for my $entry (
450     ["global", $self->{section}{global}],
451     ["program", $self->{section}{"program $program"}],
452     ["instance", $self->{section}{"instance $name"}],
453     ) {
454     my ($scope, $section) = @$entry;
455     next unless $section;
456    
457     for my $key (grep /\Auser\./, keys %$section) {
458     my ($user) = $key =~ /\Auser\.([A-Za-z_][A-Za-z0-9_-]*)\z/
459     or die "Invalid MCP user '$key'\n";
460     my $generation = $section->{$key};
461     $generation =~ /\A-?[1-9][0-9]*\z/ && length $generation < 18
462     or die "Invalid MCP user generation for '$key'\n";
463     $users{$user} = { scope => $scope, generation => $generation };
464     }
465     }
466    
467     \%users
468     }
469    
470     sub user_credentials {
471     my ($self, $name, $program) = @_;
472    
473 root 1.26 $name = AnyEvent::MCP::instance_name $name;
474 root 1.24 $program //= program_name;
475     my $users = $self->users ($name, $program);
476     my @credentials;
477    
478     for my $user (sort { ($a ne "default") <=> ($b ne "default") || $a cmp $b } keys %$users) {
479     my $info = $users->{$user};
480     next unless $info->{generation} > 0;
481     my $token = MIME::Base64::encode_base64 ($self->derive_secret (
482     32, "user-token-v1", AnyEvent::MCP::utf8enc ($program),
483     AnyEvent::MCP::utf8enc ($name), $info->{scope},
484     AnyEvent::MCP::utf8enc ($user), "$info->{generation}"
485     ), "");
486     $token =~ y!+/=!-_!d;
487     push @credentials, [$user, "$info->{scope}:$user", $token];
488     }
489    
490     \@credentials
491     }
492    
493     sub connect_user {
494     my ($self, $name, $program) = @_;
495    
496 root 1.26 $name = AnyEvent::MCP::instance_name $name;
497 root 1.24 $program //= program_name;
498     my $user = $self->{section}{"instance $name"}{connect_user}
499     // $self->{section}{"program $program"}{connect_user}
500     // $self->{section}{global}{connect_user} // "default";
501 root 1.26 user_name $user
502 root 1.24 }
503    
504 root 1.4 sub http_getopt {
505 root 1.5 my ($name, $bind, $print_url, $tokens) = @_;
506 root 1.4
507 root 1.24 "name=s" => $name, "bind=s" => $bind, "print-url|print-urls:s" => $print_url, "token=s@" => $tokens
508 root 1.4 }
509    
510 root 1.17 sub auto_rules {
511     my ($self, $name) = @_;
512     my %settings = (
513     %{$self->{section}{"program " . program_name ()} || {}},
514 root 1.26 %{$self->{section}{"instance " . AnyEvent::MCP::instance_name ($name)} || {}},
515 root 1.17 );
516     my @rules;
517    
518     for my $key (sort grep /\Aauto-rule\./, keys %settings) {
519     $key =~ /\Aauto-rule\.(.+)\z/
520     or die "Invalid automatic permission rule key '$key'\n";
521     my $value = $settings{$key};
522     next unless length $value;
523     my ($decision, $spec) = $value =~ /\A(allow|deny)\s+(\S+)\z/
524     or die "Invalid configured automatic permission rule '$key'\n";
525     push @rules, [$decision, $spec];
526     }
527    
528     @rules
529     }
530    
531 root 1.4 sub server_port {
532     my ($self, $name) = @_;
533    
534 root 1.26 $name = AnyEvent::MCP::instance_name $name;
535 root 1.4
536 root 1.21 16384 + (
537     (unpack "n", $self->derive_secret (
538     2,
539     "port",
540     (AnyEvent::MCP::utf8enc program_name),
541     (AnyEvent::MCP::utf8enc $name))
542     ) & 16383)
543 root 1.4 }
544    
545     sub http_settings {
546     my ($self, $name, $bind) = @_;
547    
548 root 1.26 $name = AnyEvent::MCP::instance_name $name;
549 root 1.14 my $settings = {
550     %{ $self->{section}{"program " . program_name ()} },
551     %{ $self->{section}{"instance $name"} },
552     };
553 root 1.4 my $port = $self->server_port ($name);
554     my $configured = $settings->{bind} // "127.0.0.1:$port";
555     $configured = "127.0.0.1$configured" if $configured =~ /\A:[0-9]+\z/;
556     my ($configured_host, $configured_port) = AnyEvent::Socket::parse_hostport $configured, $port;
557     defined $configured_host && defined $configured_port
558     && $configured_port =~ /\A[0-9]+\z/ && $configured_port <= 65535
559     or die "Invalid MCP bind address '$configured'\n";
560    
561     $bind = $configured unless defined $bind;
562     $bind = "127.0.0.1$bind" if $bind =~ /\A:[0-9]+\z/;
563    
564     my ($host, $service) = AnyEvent::Socket::parse_hostport $bind, $configured_port;
565     defined $host && defined $service && $service =~ /\A[0-9]+\z/ && $service <= 65535
566     or die "Invalid MCP bind address '$bind'\n";
567    
568     my %kv = (
569     (map { exists $settings->{$_} ? ($_ => $settings->{$_}) : () }
570 root 1.23 qw(path url_host url_scheme tls_cert tls_key)),
571 root 1.4 name => $name,
572     host => $host,
573     port => $service,
574     );
575    
576     unless (AnyEvent::Socket::parse_address $host) {
577 root 1.20 my $cv = AnyEvent->condvar;
578     AnyEvent::Socket::resolve_sockaddr $host, $service, "tcp", 0, 0, $cv;
579     my ($resolved) = $cv->recv;
580     $resolved or die "Cannot resolve MCP bind host '$host'\n";
581 root 1.4
582 root 1.20 my (undef, $address) = AnyEvent::Socket::unpack_sockaddr $resolved->[3];
583 root 1.4 $kv{url_host} //= $host;
584 root 1.9 $kv{host} = AnyEvent::Socket::format_address $address;
585 root 1.4 }
586    
587 root 1.24 if (exists $settings->{tokens}) {
588     $kv{tokens} = [split /\s*,\s*/, $settings->{tokens}, -1];
589     $kv{connect_tokens} = $kv{tokens};
590     } else {
591     my $credentials = $self->user_credentials ($name);
592     $kv{tokens} = [map $_->[2], @$credentials];
593     $kv{token_users} = { map { $_->[2] => $_->[1] } @$credentials };
594     my $connect = $self->connect_user ($name);
595     $kv{connect_tokens} = [map $_->[2], grep $_->[0] eq $connect, @$credentials];
596     }
597 root 1.4
598     %kv
599     }
600    
601     sub http_url {
602     my ($self, $name, $bind, %options) = @_;
603    
604 root 1.26 $name = AnyEvent::MCP::instance_name $name;
605 root 1.14
606 root 1.11 # only a derived token depends on the master secret surviving this process.
607 root 1.8 if (($options{auth} || "") ne "none"
608     && !exists $options{tokens}
609 root 1.14 && !exists $self->{section}{"instance $name"}{tokens}
610 root 1.24 && !exists $self->{section}{"program " . program_name ()}{tokens}
611     && !exists $self->{section}{global}{tokens}) {
612 root 1.30 my $stored = (ref $self)->new (file => $self->{file})->load;
613 root 1.8 $stored->{section}{global}{secret} eq $self->{section}{global}{secret}
614     or die "MCP master secret is not persistently stored in $self->{file}\n";
615     }
616 root 1.4
617     require AnyEvent::MCP::Server::HTTP;
618 root 1.8 my %http = $self->http_settings ($name, $bind);
619     delete $http{tokens} if ($options{auth} || "") eq "none";
620 root 1.11
621     AnyEvent::MCP::Server::HTTP->new (%http, %options)->url
622 root 1.4 }
623    
624 root 1.1
625     1