2014-04-14 15:26:48 +01:00
|
|
|
{ config, lib, pkgs, ... }:
|
2010-02-01 17:05:02 +00:00
|
|
|
|
2014-04-14 15:26:48 +01:00
|
|
|
with lib;
|
2009-03-06 12:26:08 +00:00
|
|
|
|
2007-01-07 10:19:16 +00:00
|
|
|
let
|
2009-03-06 12:26:08 +00:00
|
|
|
|
2012-03-25 16:42:05 +01:00
|
|
|
cfg = config.services.openssh;
|
|
|
|
cfgc = config.programs.ssh;
|
2009-03-06 12:26:08 +00:00
|
|
|
|
|
|
|
nssModulesPath = config.system.nssModules.path;
|
2007-01-07 10:19:16 +00:00
|
|
|
|
2011-11-29 06:08:55 +00:00
|
|
|
userOptions = {
|
2012-08-17 18:48:22 +01:00
|
|
|
|
2011-11-29 06:08:55 +00:00
|
|
|
openssh.authorizedKeys = {
|
|
|
|
keys = mkOption {
|
2013-10-30 16:37:45 +00:00
|
|
|
type = types.listOf types.str;
|
2011-11-29 06:08:55 +00:00
|
|
|
default = [];
|
|
|
|
description = ''
|
2012-11-20 14:13:17 +00:00
|
|
|
A list of verbatim OpenSSH public keys that should be added to the
|
|
|
|
user's authorized keys. The keys are added to a file that the SSH
|
|
|
|
daemon reads in addition to the the user's authorized_keys file.
|
|
|
|
You can combine the <literal>keys</literal> and
|
2011-11-29 06:08:55 +00:00
|
|
|
<literal>keyFiles</literal> options.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
keyFiles = mkOption {
|
2014-05-01 22:27:16 +01:00
|
|
|
type = types.listOf types.path;
|
2011-11-29 06:08:55 +00:00
|
|
|
default = [];
|
|
|
|
description = ''
|
2012-11-20 14:13:17 +00:00
|
|
|
A list of files each containing one OpenSSH public key that should be
|
|
|
|
added to the user's authorized keys. The contents of the files are
|
|
|
|
read at build time and added to a file that the SSH daemon reads in
|
|
|
|
addition to the the user's authorized_keys file. You can combine the
|
|
|
|
<literal>keyFiles</literal> and <literal>keys</literal> options.
|
2011-11-29 06:08:55 +00:00
|
|
|
'';
|
|
|
|
};
|
|
|
|
};
|
2012-08-17 18:48:22 +01:00
|
|
|
|
2011-11-29 06:08:55 +00:00
|
|
|
};
|
|
|
|
|
2012-12-11 16:14:52 +00:00
|
|
|
authKeysFiles = let
|
2015-08-27 14:24:14 +01:00
|
|
|
mkAuthKeyFile = u: nameValuePair "ssh/authorized_keys.d/${u.name}" {
|
2014-06-27 08:19:30 +01:00
|
|
|
mode = "0444";
|
2012-11-20 14:13:17 +00:00
|
|
|
source = pkgs.writeText "${u.name}-authorized_keys" ''
|
|
|
|
${concatStringsSep "\n" u.openssh.authorizedKeys.keys}
|
2013-11-12 12:48:19 +00:00
|
|
|
${concatMapStrings (f: readFile f + "\n") u.openssh.authorizedKeys.keyFiles}
|
2012-11-20 14:13:17 +00:00
|
|
|
'';
|
|
|
|
};
|
|
|
|
usersWithKeys = attrValues (flip filterAttrs config.users.extraUsers (n: u:
|
|
|
|
length u.openssh.authorizedKeys.keys != 0 || length u.openssh.authorizedKeys.keyFiles != 0
|
|
|
|
));
|
2015-08-27 14:24:14 +01:00
|
|
|
in listToAttrs (map mkAuthKeyFile usersWithKeys);
|
2011-11-29 06:08:55 +00:00
|
|
|
|
2016-02-01 15:27:46 +00:00
|
|
|
supportOldHostKeys = !versionAtLeast config.system.stateVersion "15.07";
|
|
|
|
|
2007-01-07 10:19:16 +00:00
|
|
|
in
|
2006-11-23 17:43:28 +00:00
|
|
|
|
2009-07-15 16:53:39 +01:00
|
|
|
{
|
2007-06-08 16:41:12 +01:00
|
|
|
|
2009-07-15 16:53:39 +01:00
|
|
|
###### interface
|
2011-07-12 11:34:30 +01:00
|
|
|
|
2009-07-15 16:53:39 +01:00
|
|
|
options = {
|
2011-07-12 11:34:30 +01:00
|
|
|
|
2010-03-11 17:02:49 +00:00
|
|
|
services.openssh = {
|
2009-07-15 16:53:39 +01:00
|
|
|
|
|
|
|
enable = mkOption {
|
2013-10-30 16:37:45 +00:00
|
|
|
type = types.bool;
|
2009-07-15 16:53:39 +01:00
|
|
|
default = false;
|
|
|
|
description = ''
|
2010-03-11 17:02:49 +00:00
|
|
|
Whether to enable the OpenSSH secure shell daemon, which
|
|
|
|
allows secure remote logins.
|
2009-07-15 16:53:39 +01:00
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
2014-04-22 15:07:53 +01:00
|
|
|
startWhenNeeded = mkOption {
|
|
|
|
type = types.bool;
|
|
|
|
default = false;
|
|
|
|
description = ''
|
|
|
|
If set, <command>sshd</command> is socket-activated; that
|
|
|
|
is, instead of having it permanently running as a daemon,
|
|
|
|
systemd will start an instance for each incoming connection.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
2009-07-15 16:53:39 +01:00
|
|
|
forwardX11 = mkOption {
|
2013-10-30 16:37:45 +00:00
|
|
|
type = types.bool;
|
2016-09-05 17:17:22 +01:00
|
|
|
default = false;
|
2009-07-15 16:53:39 +01:00
|
|
|
description = ''
|
|
|
|
Whether to allow X11 connections to be forwarded.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
allowSFTP = mkOption {
|
2013-10-30 16:37:45 +00:00
|
|
|
type = types.bool;
|
2009-07-15 16:53:39 +01:00
|
|
|
default = true;
|
|
|
|
description = ''
|
|
|
|
Whether to enable the SFTP subsystem in the SSH daemon. This
|
|
|
|
enables the use of commands such as <command>sftp</command> and
|
|
|
|
<command>sshfs</command>.
|
|
|
|
'';
|
|
|
|
};
|
2006-11-23 17:43:28 +00:00
|
|
|
|
2009-07-15 16:53:39 +01:00
|
|
|
permitRootLogin = mkOption {
|
2016-10-01 18:23:56 +01:00
|
|
|
default = "prohibit-password";
|
|
|
|
type = types.enum ["yes" "without-password" "prohibit-password" "forced-commands-only" "no"];
|
2009-07-15 16:53:39 +01:00
|
|
|
description = ''
|
2015-03-19 16:03:09 +00:00
|
|
|
Whether the root user can login using ssh.
|
2009-07-15 16:53:39 +01:00
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
gatewayPorts = mkOption {
|
2013-10-30 16:37:45 +00:00
|
|
|
type = types.str;
|
2009-07-15 16:53:39 +01:00
|
|
|
default = "no";
|
|
|
|
description = ''
|
|
|
|
Specifies whether remote hosts are allowed to connect to
|
|
|
|
ports forwarded for the client. See
|
|
|
|
<citerefentry><refentrytitle>sshd_config</refentrytitle>
|
|
|
|
<manvolnum>5</manvolnum></citerefentry>.
|
|
|
|
'';
|
|
|
|
};
|
2009-12-10 14:45:41 +00:00
|
|
|
|
|
|
|
ports = mkOption {
|
2013-10-30 16:37:45 +00:00
|
|
|
type = types.listOf types.int;
|
2009-12-10 14:45:41 +00:00
|
|
|
default = [22];
|
|
|
|
description = ''
|
|
|
|
Specifies on which ports the SSH daemon listens.
|
|
|
|
'';
|
|
|
|
};
|
2011-07-12 11:34:27 +01:00
|
|
|
|
2014-08-31 12:15:39 +01:00
|
|
|
listenAddresses = mkOption {
|
2016-09-11 10:08:31 +01:00
|
|
|
type = with types; listOf (submodule {
|
|
|
|
options = {
|
|
|
|
addr = mkOption {
|
|
|
|
type = types.nullOr types.str;
|
|
|
|
default = null;
|
|
|
|
description = ''
|
|
|
|
Host, IPv4 or IPv6 address to listen to.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
port = mkOption {
|
|
|
|
type = types.nullOr types.int;
|
|
|
|
default = null;
|
|
|
|
description = ''
|
|
|
|
Port to listen to.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
};
|
|
|
|
});
|
2014-08-31 12:15:39 +01:00
|
|
|
default = [];
|
|
|
|
example = [ { addr = "192.168.3.1"; port = 22; } { addr = "0.0.0.0"; port = 64022; } ];
|
|
|
|
description = ''
|
|
|
|
List of addresses and ports to listen on (ListenAddress directive
|
|
|
|
in config). If port is not specified for address sshd will listen
|
2014-09-01 12:02:39 +01:00
|
|
|
on all ports specified by <literal>ports</literal> option.
|
|
|
|
NOTE: this will override default listening on all local addresses and port 22.
|
2014-08-31 16:21:14 +01:00
|
|
|
NOTE: setting this option won't automatically enable given ports
|
|
|
|
in firewall configuration.
|
2014-08-31 12:15:39 +01:00
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
2011-07-12 11:34:27 +01:00
|
|
|
passwordAuthentication = mkOption {
|
2013-10-30 16:37:45 +00:00
|
|
|
type = types.bool;
|
2011-07-12 11:34:27 +01:00
|
|
|
default = true;
|
|
|
|
description = ''
|
2013-10-15 14:05:49 +01:00
|
|
|
Specifies whether password authentication is allowed.
|
2011-07-12 11:34:27 +01:00
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
2012-04-01 11:54:17 +01:00
|
|
|
challengeResponseAuthentication = mkOption {
|
2013-10-30 16:37:45 +00:00
|
|
|
type = types.bool;
|
2012-04-01 11:54:17 +01:00
|
|
|
default = true;
|
|
|
|
description = ''
|
|
|
|
Specifies whether challenge/response authentication is allowed.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
2013-08-24 00:01:10 +01:00
|
|
|
hostKeys = mkOption {
|
2013-10-30 16:37:45 +00:00
|
|
|
type = types.listOf types.attrs;
|
2013-08-24 00:01:10 +01:00
|
|
|
default =
|
2015-07-27 19:13:08 +01:00
|
|
|
[ { type = "rsa"; bits = 4096; path = "/etc/ssh/ssh_host_rsa_key"; }
|
|
|
|
{ type = "ed25519"; path = "/etc/ssh/ssh_host_ed25519_key"; }
|
2016-02-01 15:27:46 +00:00
|
|
|
] ++ optionals supportOldHostKeys
|
2015-07-27 19:13:08 +01:00
|
|
|
[ { type = "dsa"; path = "/etc/ssh/ssh_host_dsa_key"; }
|
|
|
|
{ type = "ecdsa"; bits = 521; path = "/etc/ssh/ssh_host_ecdsa_key"; }
|
2013-08-24 00:01:10 +01:00
|
|
|
];
|
2012-05-09 23:13:53 +01:00
|
|
|
description = ''
|
2013-08-24 00:01:10 +01:00
|
|
|
NixOS can automatically generate SSH host keys. This option
|
|
|
|
specifies the path, type and size of each key. See
|
|
|
|
<citerefentry><refentrytitle>ssh-keygen</refentrytitle>
|
|
|
|
<manvolnum>1</manvolnum></citerefentry> for supported types
|
|
|
|
and sizes.
|
2012-05-09 23:13:53 +01:00
|
|
|
'';
|
2012-02-22 20:28:54 +00:00
|
|
|
};
|
|
|
|
|
2012-12-11 16:29:34 +00:00
|
|
|
authorizedKeysFiles = mkOption {
|
2014-05-01 22:27:16 +01:00
|
|
|
type = types.listOf types.str;
|
2012-12-11 16:29:34 +00:00
|
|
|
default = [];
|
2016-05-12 16:01:17 +01:00
|
|
|
description = "Files from which authorized keys are read.";
|
2012-12-11 16:29:34 +00:00
|
|
|
};
|
|
|
|
|
2010-10-18 11:31:41 +01:00
|
|
|
extraConfig = mkOption {
|
2013-10-30 16:37:45 +00:00
|
|
|
type = types.lines;
|
2010-10-18 11:31:41 +01:00
|
|
|
default = "";
|
|
|
|
description = "Verbatim contents of <filename>sshd_config</filename>.";
|
|
|
|
};
|
2011-07-12 11:34:30 +01:00
|
|
|
|
2015-05-22 13:23:21 +01:00
|
|
|
moduliFile = mkOption {
|
|
|
|
example = "services.openssh.moduliFile = /etc/my-local-ssh-moduli;";
|
|
|
|
type = types.path;
|
|
|
|
description = ''
|
|
|
|
Path to <literal>moduli</literal> file to install in
|
|
|
|
<literal>/etc/ssh/moduli</literal>. If this option is unset, then
|
|
|
|
the <literal>moduli</literal> file shipped with OpenSSH will be used.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
2009-07-15 16:53:39 +01:00
|
|
|
};
|
|
|
|
|
2015-09-02 16:32:38 +01:00
|
|
|
users.users = mkOption {
|
2011-11-29 06:08:55 +00:00
|
|
|
options = [ userOptions ];
|
|
|
|
};
|
|
|
|
|
2009-07-15 16:53:39 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
###### implementation
|
|
|
|
|
2013-08-24 00:01:10 +01:00
|
|
|
config = mkIf cfg.enable {
|
2009-07-15 16:53:39 +01:00
|
|
|
|
2015-04-19 20:15:33 +01:00
|
|
|
users.extraUsers.sshd =
|
2015-05-13 15:22:53 +01:00
|
|
|
{ isSystemUser = true;
|
|
|
|
description = "SSH privilege separation user";
|
2009-07-15 16:53:39 +01:00
|
|
|
};
|
2009-03-06 12:26:13 +00:00
|
|
|
|
2015-05-22 13:23:21 +01:00
|
|
|
services.openssh.moduliFile = mkDefault "${cfgc.package}/etc/ssh/moduli";
|
|
|
|
|
2015-08-27 14:24:14 +01:00
|
|
|
environment.etc = authKeysFiles //
|
|
|
|
{ "ssh/moduli".source = cfg.moduliFile; };
|
2010-02-01 17:05:02 +00:00
|
|
|
|
2014-04-22 15:07:53 +01:00
|
|
|
systemd =
|
|
|
|
let
|
2016-10-21 04:12:21 +01:00
|
|
|
sshd-service =
|
2014-04-22 15:07:53 +01:00
|
|
|
{ description = "SSH Daemon";
|
|
|
|
|
|
|
|
wantedBy = optional (!cfg.startWhenNeeded) "multi-user.target";
|
|
|
|
|
|
|
|
stopIfChanged = false;
|
|
|
|
|
2014-09-12 05:43:58 +01:00
|
|
|
path = [ cfgc.package pkgs.gawk ];
|
2014-04-22 15:07:53 +01:00
|
|
|
|
|
|
|
environment.LD_LIBRARY_PATH = nssModulesPath;
|
|
|
|
|
2016-10-21 04:12:21 +01:00
|
|
|
wants = [ "sshd-keygen.service" ];
|
|
|
|
after = [ "sshd-keygen.service" ];
|
2014-04-22 15:07:53 +01:00
|
|
|
|
|
|
|
serviceConfig =
|
|
|
|
{ ExecStart =
|
2016-06-19 10:19:31 +01:00
|
|
|
(optionalString cfg.startWhenNeeded "-") +
|
2016-03-06 04:56:32 +00:00
|
|
|
"${cfgc.package}/bin/sshd " + (optionalString cfg.startWhenNeeded "-i ") +
|
2014-04-22 15:07:53 +01:00
|
|
|
"-f ${pkgs.writeText "sshd_config" cfg.extraConfig}";
|
|
|
|
KillMode = "process";
|
|
|
|
} // (if cfg.startWhenNeeded then {
|
|
|
|
StandardInput = "socket";
|
|
|
|
} else {
|
|
|
|
Restart = "always";
|
2016-12-29 14:49:19 +00:00
|
|
|
Type = "simple";
|
2014-04-22 15:07:53 +01:00
|
|
|
});
|
|
|
|
};
|
2016-10-21 04:12:21 +01:00
|
|
|
|
|
|
|
sshd-keygen-service =
|
|
|
|
{ description = "SSH Host Key Generation";
|
|
|
|
path = [ cfgc.package ];
|
|
|
|
script =
|
|
|
|
''
|
|
|
|
mkdir -m 0755 -p /etc/ssh
|
|
|
|
${flip concatMapStrings cfg.hostKeys (k: ''
|
|
|
|
if ! [ -f "${k.path}" ]; then
|
|
|
|
ssh-keygen -t "${k.type}" ${if k ? bits then "-b ${toString k.bits}" else ""} -f "${k.path}" -N ""
|
|
|
|
fi
|
|
|
|
'')}
|
|
|
|
'';
|
|
|
|
|
|
|
|
serviceConfig = {
|
|
|
|
Type = "oneshot";
|
|
|
|
RemainAfterExit = "yes";
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2014-04-22 15:07:53 +01:00
|
|
|
in
|
2012-06-18 20:28:31 +01:00
|
|
|
|
2014-04-22 15:07:53 +01:00
|
|
|
if cfg.startWhenNeeded then {
|
2013-01-05 00:05:25 +00:00
|
|
|
|
2014-04-22 15:07:53 +01:00
|
|
|
sockets.sshd =
|
|
|
|
{ description = "SSH Socket";
|
|
|
|
wantedBy = [ "sockets.target" ];
|
|
|
|
socketConfig.ListenStream = cfg.ports;
|
|
|
|
socketConfig.Accept = true;
|
|
|
|
};
|
2012-08-17 18:48:22 +01:00
|
|
|
|
2016-10-21 04:12:21 +01:00
|
|
|
services.sshd-keygen = sshd-keygen-service;
|
|
|
|
services."sshd@" = sshd-service;
|
2012-06-18 20:28:31 +01:00
|
|
|
|
2014-04-22 15:07:53 +01:00
|
|
|
} else {
|
2012-06-18 20:28:31 +01:00
|
|
|
|
2016-10-21 04:12:21 +01:00
|
|
|
services.sshd-keygen = sshd-keygen-service;
|
|
|
|
services.sshd = sshd-service;
|
2012-06-18 20:28:31 +01:00
|
|
|
|
|
|
|
};
|
|
|
|
|
2010-02-01 17:05:02 +00:00
|
|
|
networking.firewall.allowedTCPPorts = cfg.ports;
|
2010-10-18 11:31:41 +01:00
|
|
|
|
2013-10-15 14:05:49 +01:00
|
|
|
security.pam.services.sshd =
|
2014-04-22 14:39:11 +01:00
|
|
|
{ startSession = true;
|
2013-10-15 14:05:49 +01:00
|
|
|
showMotd = true;
|
|
|
|
unixAuth = cfg.passwordAuthentication;
|
|
|
|
};
|
2012-08-17 18:48:22 +01:00
|
|
|
|
2012-12-11 16:29:34 +00:00
|
|
|
services.openssh.authorizedKeysFiles =
|
|
|
|
[ ".ssh/authorized_keys" ".ssh/authorized_keys2" "/etc/ssh/authorized_keys.d/%u" ];
|
|
|
|
|
2016-02-23 17:03:33 +00:00
|
|
|
services.openssh.extraConfig = mkOrder 0
|
2010-10-18 11:31:41 +01:00
|
|
|
''
|
|
|
|
Protocol 2
|
|
|
|
|
2013-10-15 14:05:49 +01:00
|
|
|
UsePAM yes
|
2010-10-18 11:31:41 +01:00
|
|
|
|
2015-03-09 10:26:18 +00:00
|
|
|
UsePrivilegeSeparation sandbox
|
|
|
|
|
2012-10-24 18:01:27 +01:00
|
|
|
AddressFamily ${if config.networking.enableIPv6 then "any" else "inet"}
|
2010-10-18 11:31:41 +01:00
|
|
|
${concatMapStrings (port: ''
|
|
|
|
Port ${toString port}
|
|
|
|
'') cfg.ports}
|
|
|
|
|
2015-04-14 23:20:38 +01:00
|
|
|
${concatMapStrings ({ port, addr, ... }: ''
|
2014-08-31 12:15:39 +01:00
|
|
|
ListenAddress ${addr}${if port != null then ":" + toString port else ""}
|
|
|
|
'') cfg.listenAddresses}
|
|
|
|
|
2012-03-25 16:42:05 +01:00
|
|
|
${optionalString cfgc.setXAuthLocation ''
|
|
|
|
XAuthLocation ${pkgs.xorg.xauth}/bin/xauth
|
|
|
|
''}
|
|
|
|
|
2010-10-18 11:31:41 +01:00
|
|
|
${if cfg.forwardX11 then ''
|
|
|
|
X11Forwarding yes
|
|
|
|
'' else ''
|
|
|
|
X11Forwarding no
|
|
|
|
''}
|
|
|
|
|
|
|
|
${optionalString cfg.allowSFTP ''
|
2014-09-12 05:43:58 +01:00
|
|
|
Subsystem sftp ${cfgc.package}/libexec/sftp-server
|
2010-10-18 11:31:41 +01:00
|
|
|
''}
|
|
|
|
|
|
|
|
PermitRootLogin ${cfg.permitRootLogin}
|
|
|
|
GatewayPorts ${cfg.gatewayPorts}
|
2011-07-12 11:34:27 +01:00
|
|
|
PasswordAuthentication ${if cfg.passwordAuthentication then "yes" else "no"}
|
2012-04-01 11:54:17 +01:00
|
|
|
ChallengeResponseAuthentication ${if cfg.challengeResponseAuthentication then "yes" else "no"}
|
2012-10-23 14:10:48 +01:00
|
|
|
|
|
|
|
PrintMotd no # handled by pam_motd
|
2012-12-11 16:40:39 +00:00
|
|
|
|
2012-12-11 16:29:34 +00:00
|
|
|
AuthorizedKeysFile ${toString cfg.authorizedKeysFiles}
|
2013-08-24 00:01:10 +01:00
|
|
|
|
|
|
|
${flip concatMapStrings cfg.hostKeys (k: ''
|
|
|
|
HostKey ${k.path}
|
|
|
|
'')}
|
2016-02-01 15:27:46 +00:00
|
|
|
|
|
|
|
# Allow DSA client keys for now. (These were deprecated
|
|
|
|
# in OpenSSH 7.0.)
|
|
|
|
PubkeyAcceptedKeyTypes +ssh-dss
|
|
|
|
|
|
|
|
# Re-enable DSA host keys for now.
|
|
|
|
${optionalString supportOldHostKeys ''
|
|
|
|
HostKeyAlgorithms +ssh-dss
|
|
|
|
''}
|
2010-10-18 11:31:41 +01:00
|
|
|
'';
|
|
|
|
|
2012-04-26 09:13:24 +01:00
|
|
|
assertions = [{ assertion = if cfg.forwardX11 then cfgc.setXAuthLocation else true;
|
2014-04-27 23:01:06 +01:00
|
|
|
message = "cannot enable X11 forwarding without setting xauth location";}]
|
2015-04-14 23:20:38 +01:00
|
|
|
++ flip map cfg.listenAddresses ({ addr, port, ... }: {
|
2014-08-31 12:15:39 +01:00
|
|
|
assertion = addr != null;
|
2014-09-02 09:06:04 +01:00
|
|
|
message = "addr must be specified in each listenAddresses entry";
|
2014-04-27 23:01:06 +01:00
|
|
|
});
|
2012-10-23 14:10:48 +01:00
|
|
|
|
2009-03-06 12:26:08 +00:00
|
|
|
};
|
2009-07-15 16:53:39 +01:00
|
|
|
|
2006-11-23 17:43:28 +00:00
|
|
|
}
|