{ config, lib, pkgs, ... }:

with builtins;
with lib;
let
  cfg = config.tv.charybdis;

  out = {
    options.tv.charybdis = api;
    config = mkIf cfg.enable (mkMerge [
      imp
      { tv.iptables.input-retiolum-accept-new-tcp = [ 6667 6697 ]; }
    ]);
  };

  api = {
    enable = mkEnableOption "tv.charybdis";
    dataDir = mkOption {
      type = types.str;
      default = "/var/lib/charybdis";
    };
    dhParams = mkOption {
      type = types.str;
      default = "/root/src/secrets/charybdis.dh.pem";
    };
    motd = mkOption {
      type = types.str;
      default = "/join #retiolum";
    };
    sslCert = mkOption {
      type = types.path;
    };
    sslKey = mkOption {
      type = types.str;
      default = "/root/src/secrets/charybdis.key.pem";
    };
  };

  imp = {
    systemd.services.charybdis = {
      wantedBy = [ "multi-user.target" ];
      environment = {
        BANDB_DBPATH = "${cfg.dataDir}/ban.db";
      };
      serviceConfig = {
        PermissionsStartOnly = "true";
        SyslogIdentifier = "charybdis";
        User = user.name;
        PrivateTmp = "true";
        Restart = "always";
        ExecStartPre = pkgs.writeScript "charybdis-init" ''
          #! /bin/sh
          mkdir -p ${cfg.dataDir}
          chown ${user.name}: ${cfg.dataDir}
          install -o ${user.name} -m 0400 ${cfg.sslKey} /tmp/ssl.key
          install -o ${user.name} -m 0400 ${cfg.dhParams} /tmp/dh.pem
          echo ${escapeShellArg cfg.motd} > /tmp/ircd.motd
        '';
        ExecStart = pkgs.writeScript "charybdis-service" ''
          #! /bin/sh
          set -euf
          exec ${Zpkgs.charybdis}/bin/charybdis-ircd \
            -foreground \
            -logfile /dev/stderr \
            -configfile ${configFile}
        '';
      };
    };

    users.extraUsers = singleton {
      inherit (user) name uid;
    };
  };

  user = {
    name = "charybdis";
    uid = 3748224544; # genid charybdis
  };

  configFile = toFile "charybdis-ircd.conf" ''
    /* doc/example.conf - brief example configuration file
     *
     * Copyright (C) 2000-2002 Hybrid Development Team
     * Copyright (C) 2002-2005 ircd-ratbox development team
     * Copyright (C) 2005-2006 charybdis development team
     *
     * $Id: example.conf 3582 2007-11-17 21:55:48Z jilles $
     *
     * See reference.conf for more information.
     */
    
    /* Extensions */
    #loadmodule "extensions/chm_operonly_compat.so";
    #loadmodule "extensions/chm_quietunreg_compat.so";
    #loadmodule "extensions/chm_sslonly_compat.so";
    #loadmodule "extensions/createauthonly.so";
    #loadmodule "extensions/extb_account.so";
    #loadmodule "extensions/extb_canjoin.so";
    #loadmodule "extensions/extb_channel.so";
    #loadmodule "extensions/extb_extgecos.so";
    #loadmodule "extensions/extb_oper.so";
    #loadmodule "extensions/extb_realname.so";
    #loadmodule "extensions/extb_server.so";
    #loadmodule "extensions/extb_ssl.so";
    #loadmodule "extensions/hurt.so";
    #loadmodule "extensions/m_findforwards.so";
    #loadmodule "extensions/m_identify.so";
    #loadmodule "extensions/no_oper_invis.so";
    #loadmodule "extensions/sno_farconnect.so";
    #loadmodule "extensions/sno_globalkline.so";
    #loadmodule "extensions/sno_globaloper.so";
    #loadmodule "extensions/sno_whois.so";
    loadmodule "extensions/override.so";
    
    /*
     * IP cloaking extensions: use ip_cloaking_4.0
     * if you're linking 3.2 and later, otherwise use
     * ip_cloaking.so, for compatibility with older 3.x
     * releases.
     */
    
    #loadmodule "extensions/ip_cloaking_4.0.so";
    #loadmodule "extensions/ip_cloaking.so";
    
    serverinfo {
      name = ${toJSON (head config.tv.identity.self.nets.retiolum.aliases)};
      sid = "4z3";
      description = "miep!";
      network_name = "irc.retiolum";
      #network_desc = "Retiolum IRC Network";
      hub = yes;
    
      /* On multi-homed hosts you may need the following. These define
       * the addresses we connect from to other servers. */
      /* for IPv4 */
      vhost = ${concatMapStringsSep ", " toJSON config.tv.identity.self.nets.retiolum.addrs4};
      /* for IPv6 */
      vhost6 = ${concatMapStringsSep ", " toJSON config.tv.identity.self.nets.retiolum.addrs6};
      
      /* ssl_private_key: our ssl private key */
      ssl_private_key = "/tmp/ssl.key";
    
      /* ssl_cert: certificate for our ssl server */
      ssl_cert = ${toJSON cfg.sslCert};
    
      /* ssl_dh_params: DH parameters, generate with openssl dhparam -out dh.pem 1024 */
      ssl_dh_params = "/tmp/dh.pem";
    
      /* ssld_count: number of ssld processes you want to start, if you
       * have a really busy server, using N-1 where N is the number of
       * cpu/cpu cores you have might be useful. A number greater than one
       * can also be useful in case of bugs in ssld and because ssld needs
       * two file descriptors per SSL connection.
       */
      ssld_count = 1;
    
      /* default max clients: the default maximum number of clients
       * allowed to connect.  This can be changed once ircd has started by
       * issuing:
       *   /quote set maxclients <limit>
       */
      default_max_clients = 1024;
    
      /* nicklen: enforced nickname length (for this server only; must not
       * be longer than the maximum length set while building).
       */
      nicklen = 30;
    };
    
    admin {
      name = "tv";
      description = "peer";
      email = "tv@wu.retiolum";
    };

    log {
      fname_userlog = "/dev/stderr";
      fname_fuserlog = "/dev/stderr";
      fname_operlog = "/dev/stderr";
      fname_foperlog = "/dev/stderr";
      fname_serverlog = "/dev/stderr";
      fname_klinelog = "/dev/stderr";
      fname_killlog = "/dev/stderr";
      fname_operspylog = "/dev/stderr";
      fname_ioerrorlog = "/dev/stderr";
    };
    
    /* class {} blocks MUST be specified before anything that uses them.  That
     * means they must be defined before auth {} and before connect {}.
     */
    
    class "krebs" {
      ping_time = 2 minutes;
      number_per_ident = 10;
      number_per_ip = 2048;
      number_per_ip_global = 4096;
      cidr_ipv4_bitlen = 24;
      cidr_ipv6_bitlen = 64;
      number_per_cidr = 65536;
      max_number = 3000;
      sendq = 1 megabyte;
    };
    
    class "users" {
      ping_time = 2 minutes;
      number_per_ident = 10;
      number_per_ip = 1024;
      number_per_ip_global = 4096;
      cidr_ipv4_bitlen = 24;
      cidr_ipv6_bitlen = 64;
      number_per_cidr = 65536;
      max_number = 3000;
      sendq = 400 kbytes;
    };
    
    class "opers" {
      ping_time = 5 minutes;
      number_per_ip = 10;
      max_number = 1000;
      sendq = 1 megabyte;
    };
    
    class "server" {
      ping_time = 5 minutes;
      connectfreq = 5 minutes;
      max_number = 1;
      sendq = 4 megabytes;
    };
    
    listen {
      /* defer_accept: wait for clients to send IRC handshake data before
       * accepting them.  if you intend to use software which depends on the
       * server replying first, such as BOPM, you should disable this feature.
       * otherwise, you probably want to leave it on.
       */
      defer_accept = yes;
    
      /* If you want to listen on a specific IP only, specify host.
       * host definitions apply only to the following port line.
       */
      # XXX This is stupid because only one host is allowed[?]
      #host = ''${concatMapStringsSep ", " toJSON (
      #  config.tv.identity.self.nets.retiolum.addrs
      #)};
      port = 6667;
      sslport = 6697;
    };
    
    /* auth {}: allow users to connect to the ircd (OLD I:)
     * auth {} blocks MUST be specified in order of precedence.  The first one
     * that matches a user will be used.  So place spoofs first, then specials,
     * then general access, then restricted.
     */
    auth {
      /* user: the user@host allowed to connect.  Multiple IPv4/IPv6 user
       * lines are permitted per auth block.  This is matched against the
       * hostname and IP address (using :: shortening for IPv6 and
       * prepending a 0 if it starts with a colon) and can also use CIDR
       * masks.
       */
      user = "*@10.243.0.0/12";
      user = "*@42::/16";
    
      /* password: an optional password that is required to use this block.
       * By default this is not encrypted, specify the flag "encrypted" in
       * flags = ...; below if it is.
       */
      #password = "letmein";
      
      /* spoof: fake the users user@host to be be this.  You may either
       * specify a host or a user@host to spoof to.  This is free-form,
       * just do everyone a favour and dont abuse it. (OLD I: = flag)
       */
      #spoof = "I.still.hate.packets";
    
      /* Possible flags in auth:
       * 
       * encrypted                  | password is encrypted with mkpasswd
       * spoof_notice               | give a notice when spoofing hosts
       * exceed_limit (old > flag)  | allow user to exceed class user limits
       * kline_exempt (old ^ flag)  | exempt this user from k/g/xlines&dnsbls
       * dnsbl_exempt		      | exempt this user from dnsbls
       * spambot_exempt	      | exempt this user from spambot checks
       * shide_exempt		      | exempt this user from serverhiding
       * jupe_exempt                | exempt this user from generating
       *                              warnings joining juped channels
       * resv_exempt		      | exempt this user from resvs
       * flood_exempt               | exempt this user from flood limits
       *                                     USE WITH CAUTION.
       * no_tilde     (old - flag)  | don't prefix ~ to username if no ident
       * need_ident   (old + flag)  | require ident for user in this class
       * need_ssl                   | require SSL/TLS for user in this class
       * need_sasl                  | require SASL id for user in this class
       */
      flags = kline_exempt, exceed_limit, flood_exempt;
      
      /* class: the class the user is placed in */
      class = "krebs";
    };
    
    auth {
      user = "*@*";
      class = "users";
    };
    
    /* privset {} blocks MUST be specified before anything that uses them.  That
     * means they must be defined before operator {}.
     */
    privset "local_op" {
      privs = oper:local_kill, oper:operwall;
    };
    
    privset "server_bot" {
      extends = "local_op";
      privs = oper:kline, oper:remoteban, snomask:nick_changes;
    };
    
    privset "global_op" {
      extends = "local_op";
      privs = oper:global_kill, oper:routing, oper:kline, oper:unkline, oper:xline,
        oper:resv, oper:mass_notice, oper:remoteban;
    };
    
    privset "admin" {
      extends = "global_op";
      privs = oper:admin, oper:die, oper:rehash, oper:spy, oper:override;
    };
    
    privset "aids" {
      privs = oper:override, oper:rehash;
    };
    
    operator "aids" {
      user = "*@10.243.*";
      privset = "aids";
      flags = ~encrypted;
      password = "balls";
    };
    
    operator "god" {
      /* name: the name of the oper must go above */
    
      /* user: the user@host required for this operator.  CIDR *is*
       * supported now. auth{} spoofs work here, other spoofs do not.
       * multiple user="" lines are supported.
       */
      user = "*god@127.0.0.1";
    
      /* password: the password required to oper.  Unless ~encrypted is
       * contained in flags = ...; this will need to be encrypted using 
       * mkpasswd, MD5 is supported
       */
      password = "5";
    
      /* rsa key: the public key for this oper when using Challenge.
       * A password should not be defined when this is used, see 
       * doc/challenge.txt for more information.
       */
      #rsa_public_key_file = "/usr/local/ircd/etc/oper.pub";
    
      /* umodes: the specific umodes this oper gets when they oper.
       * If this is specified an oper will not be given oper_umodes
       * These are described above oper_only_umodes in general {};
       */
      #umodes = locops, servnotice, operwall, wallop;
    
      /* fingerprint: if specified, the oper's client certificate
       * fingerprint will be checked against the specified fingerprint
       * below.
       */
      #fingerprint = "c77106576abf7f9f90cca0f63874a60f2e40a64b";
    
      /* snomask: specific server notice mask on oper up.
       * If this is specified an oper will not be given oper_snomask.
       */
      snomask = "+Zbfkrsuy";
    
      /* flags: misc options for the operator.  You may prefix an option
       * with ~ to disable it, e.g. ~encrypted.
       *
       * Default flags are encrypted.
       *
       * Available options:
       *
       * encrypted:    the password above is encrypted [DEFAULT]
       * need_ssl:     must be using SSL/TLS to oper up
       */
      flags = encrypted;
    
      /* privset: privileges set to grant */
      privset = "admin";
    };
    
    service {
      name = "services.int";
    };
    
    cluster {
      name = "*";
      flags = kline, tkline, unkline, xline, txline, unxline, resv, tresv, unresv;
    };
    
    shared {
      oper = "*@*", "*";
      flags = all, rehash;
    };
    
    /* exempt {}: IPs that are exempt from Dlines and rejectcache. (OLD d:) */
    exempt {
      ip = "127.0.0.1";
    };
    
    channel {
      use_invex = yes;
      use_except = yes;
      use_forward = yes;
      use_knock = yes;
      knock_delay = 5 minutes;
      knock_delay_channel = 1 minute;
      max_chans_per_user = 15;
      max_bans = 100;
      max_bans_large = 500;
      default_split_user_count = 0;
      default_split_server_count = 0;
      no_create_on_split = no;
      no_join_on_split = no;
      burst_topicwho = yes;
      kick_on_split_riding = no;
      only_ascii_channels = no;
      resv_forcepart = yes;
      channel_target_change = yes;
      disable_local_channels = no;
    };
    
    serverhide {
      flatten_links = yes;
      links_delay = 5 minutes;
      hidden = no;
      disable_hidden = no;
    };
    
    /* These are the blacklist settings.
     * You can have multiple combinations of host and rejection reasons.
     * They are used in pairs of one host/rejection reason.
     *
     * These settings should be adequate for most networks, and are (presently)
     * required for use on StaticBox.
     *
     * Word to the wise: Do not use blacklists like SPEWS for blocking IRC
     * connections.
     *
     * As of charybdis 2.2, you can do some keyword substitution on the rejection
     * reason. The available keyword substitutions are:
     *
     *   ''${ip}           - the user's IP
     *   ''${host}         - the user's canonical hostname
     *   ''${dnsbl-host}   - the dnsbl hostname the lookup was done against
     *   ''${nick}         - the user's nickname
     *   ''${network-name} - the name of the network
     *
     * As of charybdis 3.4, a type parameter is supported, which specifies the
     * address families the blacklist supports. IPv4 and IPv6 are supported.
     * IPv4 is currently the default as few blacklists support IPv6 operation
     * as of this writing.
     *
     * Note: AHBL (the providers of the below *.ahbl.org BLs) request that they be
     * contacted, via email, at admins@2mbit.com before using these BLs.
     * See <http://www.ahbl.org/services.php> for more information.
     */
    blacklist {
      host = "rbl.efnetrbl.org";
      type = ipv4;
      reject_reason = "''${nick}, your IP (''${ip}) is listed in EFnet's RBL. For assistance, see http://efnetrbl.org/?i=''${ip}";
    
    #	host = "ircbl.ahbl.org";
    #	type = ipv4;
    #	reject_reason = "''${nick}, your IP (''${ip}) is listed in ''${dnsbl-host} for having an open proxy. In order to protect ''${network-name} from abuse, we are not allowing connections with open proxies to connect.";
    #
    #	host = "tor.ahbl.org";
    #	type = ipv4;
    #	reject_reason = "''${nick}, your IP (''${ip}) is listed as a TOR exit node. In order to protect ''${network-name} from tor-based abuse, we are not allowing TOR exit nodes to connect to our network.";
    #
      /* Example of a blacklist that supports both IPv4 and IPv6 */
    #	host = "foobl.blacklist.invalid";
    #	type = ipv4, ipv6;
    #	reject_reason = "''${nick}, your IP (''${ip}) is listed in ''${dnsbl-host} for some reason. In order to protect ''${network-name} from abuse, we are not allowing connections listed in ''${dnsbl-host} to connect";
    };
    
    alias "NickServ" {
      target = "NickServ";
    };
    
    alias "ChanServ" {
      target = "ChanServ";
    };
    
    alias "OperServ" {
      target = "OperServ";
    };
    
    alias "MemoServ" {
      target = "MemoServ";
    };
    
    alias "NS" {
      target = "NickServ";
    };
    
    alias "CS" {
      target = "ChanServ";
    };
    
    alias "OS" {
      target = "OperServ";
    };
    
    alias "MS" {
      target = "MemoServ";
    };
    
    general {
      hide_error_messages = opers;
      hide_spoof_ips = yes;
    
      /*
       * default_umodes: umodes to enable on connect.
       * If you have enabled the new ip_cloaking_4.0 module, and you want
       * to make use of it, add +x to this option, i.e.:
       *      default_umodes = "+ix";
       *
       * If you have enabled the old ip_cloaking module, and you want
       * to make use of it, add +h to this option, i.e.:
       *	default_umodes = "+ih";
       */
      default_umodes = "+i";
    
      default_operstring = "is an IRC Operator";
      default_adminstring = "is a Server Administrator";
      servicestring = "is a Network Service";
      disable_fake_channels = no;
      tkline_expire_notices = no;
      default_floodcount = 1000;
      failed_oper_notice = yes;
      dots_in_ident=2;
      min_nonwildcard = 4;
      min_nonwildcard_simple = 3;
      max_accept = 100;
      max_monitor = 100;
      anti_nick_flood = yes;
      max_nick_time = 20 seconds;
      max_nick_changes = 5;
      anti_spam_exit_message_time = 5 minutes;
      ts_warn_delta = 30 seconds;
      ts_max_delta = 5 minutes;
      client_exit = yes;
      collision_fnc = yes;
      resv_fnc = yes;
      global_snotices = yes;
      dline_with_reason = yes;
      kline_delay = 0 seconds;
      kline_with_reason = yes;
      kline_reason = "K-Lined";
      identify_service = "NickServ@services.int";
      identify_command = "IDENTIFY";
      non_redundant_klines = yes;
      warn_no_nline = yes;
      use_propagated_bans = yes;
      stats_e_disabled = no;
      stats_c_oper_only=no;
      stats_h_oper_only=no;
      client_flood_max_lines = 16000;
      client_flood_burst_rate = 32000;
      client_flood_burst_max = 32000;
      client_flood_message_num = 32000;
      client_flood_message_time = 32000;
      use_whois_actually = no;
      oper_only_umodes = operwall, locops, servnotice;
      oper_umodes = locops, servnotice, operwall, wallop;
      oper_snomask = "+s";
      burst_away = yes;
      nick_delay = 0 seconds; # 15 minutes if you want to enable this
      reject_ban_time = 1 minute;
      reject_after_count = 3;
      reject_duration = 5 minutes;
      throttle_duration = 60;
      throttle_count = 4;
      max_ratelimit_tokens = 30;
      away_interval = 30;
    };
    
    modules {
      path = "modules";
      path = "modules/autoload";
    };
    
    exempt {
      ip = "10.243.0.0/16";
    };
  '';
  
  Zpkgs = import ../../Zpkgs/tv { inherit pkgs; };
in
out