module infix-system {
  yang-version 1.1;
  namespace "urn:infix:system:ns:yang:1.0";
  prefix infix-sys;

  import ietf-system {
    prefix sys;
  }
  import ietf-interfaces {
    prefix if;
  }
  import ietf-yang-types {
    prefix yang;
  }
  import iana-timezones {
    prefix iana-tz;
  }

  import ietf-inet-types {
    prefix inet;
    reference
      "RFC 6991: Common YANG Data Types";
  }

  include infix-system-software;

  organization "KernelKit";
  contact      "kernelkit@googlegroups.com";
  description  "Infix augments and deviations to ietf-system.";

  revision 2025-01-25 {
    description "Add DNS resolver status.";
    reference "internal";
  }
  revision 2024-11-27 {
    description "Add NTP status.";
    reference "internal";
  }
  revision 2024-09-13 {
    description "Add some informative help about different shells and security.";
    reference "internal";
  }

  revision 2024-06-15 {
    description "Merge infix-shell-types.yang to add shell-type identities.";
    reference "internal";
  }

  revision 2024-06-14 {
    description "Add support for format specifiers in hostname:
                   - %h default hostname from /etc/os-release
                   - %i value of ID from /etc/os-release
                   - %m last three octets of base MAC, e.g., c0-ff-ee

                 Add support for yescrypt and $factory$ in password.
                 The latter is a reserved string which is interpreted
                 as the device default password from the VPD EEPROM.";
    reference "internal";
  }

  revision 2024-04-12 {
    description "New type, infix:hostname, for /system/hostname (max 64 chars).";
    reference "internal";
  }

  revision 2024-02-29 {
    description "Mark infix-sys:motd as deprecated, to be replaced with type binary.";
    reference "internal";
  }

  revision 2023-10-19 {
    description "Change deviation for timezone-utc-offset from unsupported to Etc+/-HOUR
                - Unit is set to hours (tzdata compatibility)
                - Range is -12 .. 14";

    reference "internal";
  }

  revision 2023-08-15 {
    description "Add support for user login shell.

                 Update/add deviation specifications:
                  - timezone-name (use tz defs from iana-timezone.yang)
                  - timezone-utc-offset (updated path for 'not-supported')
                  - radius ('not-supported')
                  - dns-resolver port ('not-supported')
                  - authentication username (limit length and pattern)";
    reference "internal";
  }

  revision 2023-04-11 {
    description "Initial revision.";
    reference "internal";
  }

  /*
   * Identities
   */

  identity editor-type {
    description "Base identity from which specific editor types are derived.";
  }

  identity emacs {
    description "Micro Emacs clone (mg).";
    base editor-type;
  }
  identity nano {
    description "GNU Nano.";
    base editor-type;
  }
  identity vi {
    description "The classic UNIX Visual editor.";
    base editor-type;
  }

  identity shell-type {
    description "Base identity from which specific shell types are derived.";
  }

  identity bash {
    description "Bourne again shell (BASH), standard UNIX shell.";
    base shell-type;
  }
  identity clish {
    description "Dedicated switch/router shell, similar to Cisco/JunOS.";
    base shell-type;
  }
  identity sh {
    description "POSIX shell, the original plain UNIX shell.";
    base shell-type;
  }
  identity false {
    description "Shell login disabled, both console and SSH.";
    base shell-type;
  }

  typedef source-state {
    reference "rfc5905: Network Time Protocol Version 4: Protocol and Algorithms Specification";
    type enumeration {
      enum selected {
        description "Selected NTP server for synchronization";
      }
      enum candidate {
        description "Valid for synchronization, is used together with thes elected best server and also acting as backup";
      }
      enum outlier {
        description "Valid timesource but discarded by the clustering algorithm";
      }
      enum unusable {
        description "Unreachable or unresponsable";
      }
      enum falseticker {
        description "suspected of providing incorrect time";
      }
      enum unstable {
        description "Too variable or unstable source";
      }
    }
  }
  typedef source-mode {
    type enumeration  {
      enum server {
        description "Normal NTP server";
      }
      enum peer {
        description "Symmetric peer";
      }
      enum local-clock {
        description "Reference clock";
      }
    }
}
  /*
   * Typedefs
   */

  typedef crypt-hash {
    type string {
      pattern
        '$0$.*'
      + '|$1$[a-zA-Z0-9./]{1,8}$[a-zA-Z0-9./]{22}'
      + '|$5$(rounds=\d+$)?[a-zA-Z0-9./]{1,16}$[a-zA-Z0-9./]{43}'
      + '|$6$(rounds=\d+$)?[a-zA-Z0-9./]{1,16}$[a-zA-Z0-9./]{86}'
      + '|$y$[a-zA-Z0-9./]+$[a-zA-Z0-9./]{1,86}$[a-zA-Z0-9./]{43}'
      + '|$factory$.*';
    }
    description
        "This type is used to store passwords using a hash function.  It
         extends the IANA crypt-hash type to support yescrypt as well as
         a reserved string '$factory$', used for device-specific factory
         default hash.  It is up to the underlying system to define this
         further, one example is to use Vital Product Data (VPD), e.g.,
         an onboard EEPROM where a device hash is stored for the initial
         'admin' user.

         A value of this type matches one of the forms:

           $0$<clear text password>
           $<id>$<salt>$<password hash>
           $<id>$<parameter>$<salt>$<password hash>

         The '$0$' prefix signals that the value is clear text, and even
         though it is supported, it is *not* recommended!  When such a
         value is received it passes through multiple subsystems before
         a hash value is calculated and the string '$<id>$<salt>$' or
         $<id>$<parameter>$<salt>$ is prepended and the result is stored
         in the configuration data store.  The hash function used depend
         on end system requirements.

         When any other '$<id>$' prefix is received, the system store it
         'as is' in the configuration data store.

         When a server needs to verify a password given by a user, it
         finds the stored password hash string for that user, extracts
         the salt, and calculates the hash with the salt and given
         password as input.  If the calculated hash value is the same as
         the stored value, the password given by the client is accepted.

         This type defines the following supported hash functions:

           id | hash function | feature
           ---+---------------+-------------------
            1 | MD5           | crypt-hash-md5
            5 | SHA-256       | crypt-hash-sha-256
            6 | SHA-512       | crypt-hash-sha-512
            y | yescrypt      | crypt-hash-yescrypt";
    reference
        "IEEE Std 1003.1-2008 - crypt() function
         RFC 1321: The MD5 Message-Digest Algorithm
         FIPS.180-4.2012: Secure Hash Standard (SHS)";
  }

  typedef username {
    type string {
      pattern "[_a-zA-Z0-9][-._a-zA-Z0-9]*$?";
      length "1..256";
    }
  }

  typedef hostname {
    type string {
      pattern
          '('
        + '(([a-zA-Z0-9_]([a-zA-Z0-9\-_]){0,61})?[a-zA-Z0-9]\.)*'
        + '([a-zA-Z0-9_]([a-zA-Z0-9\-_]){0,61})?[a-zA-Z0-9]\.?'
        + '|%[him]'
        + ')'
        + '([a-zA-Z0-9\-_\.]|%[him])*';
      length "1..128";
    }
    description "Linux have the same restrictions as IETF, only shorter.
                 Format specifiers are for, default hostname, ID, and the
                 last three octets in base MAC, e.g., c0-ff-ee";
  }

  /*
   * Data nodes
   */

  augment "/sys:system" {
    description "Augment of ietf-system for modifying /etc/motd.";

    leaf motd {
      description "Legacy MotD (Message of the Day), shown after login.

                   Please note, this is obsolete!  When set it overrides the
                   'motd-banner' setting for backwards compatibility.

                   Instead, use 'motd-banner', which takes a base64 encoded
                   text file as an argument.  For CLI users, edit with the
                   'text-editor motd-banner' command.";
      status obsolete;      // Replaced with motd-banner (binary)
      type string;
    }

    // From openconfig-system, which also has login-banner (TODO)
    leaf motd-banner {
      description "Message of the Day (MotD), shown after SSH/console login.

                   Base64 encoded (binary) file contents for the system file
                   /etc/motd, displayed after SSH/console login.

                   They system may append additional standard information such
                   as the current system date and time, uptime, last login
                   timestamp, etc.";
      type binary;
    }

    leaf text-editor {
      description "Text editor to use in CLI for text-editor command.";
      type identityref {
        base editor-type;
      }
      default emacs;
    }
  }

  augment "/sys:system/sys:authentication/sys:user" {
    description "Augment of ietf-system to support setting login shell for users.";
    leaf shell {
      type identityref {
        base shell-type;
      }
      default false;
      description "Set UNIX login shell for user.

                   For security reasons avoid shells bash and sh for non-admin users.
                   This since this may open the system for local security issues.

                   default: none (security)";
    }
  }

  augment "/sys:system-state" {
    container ntp {
      description "NTP status";
      config false;

      container sources {
        list source {
          key address;
          leaf address {
            type inet:ip-address;
            description "Address to NTP server";
          }
          leaf mode {
            type source-mode;
            description "Source mode";
          }
          leaf state {
            type source-state;
            description "Source state";
          }
          leaf stratum {
            type uint8;
            description "Stratum of NTP server";
          }
          leaf poll {
            type uint8;
            description "Interval between transmitted NTP messages, expressed as a power of two in seconds";
          }
        }
      }
    }

    container dns-resolver {
      description "List of active DNS servers";
      config false;

      container options {
        description "Resolver options.";

        leaf timeout {
          description "Number of seconds before resolves tries another server.";
          type uint8;
          units "seconds";
        }
        leaf attempts {
          description "Number of times the resolver reties before giving up.";
          type uint8;
        }
      }

      leaf-list search {
        type inet:domain-name;
        description "Ordered list of domains to search when resolving a host name.";
      }

      list server {
        key "address";

        leaf address {
          description "IP address of DNS server";
          type inet:ip-address;
        }

        leaf origin {
          description "How DNS server was acquired";
          type enumeration {
            enum static {
              description "Statically configured";
            }
            enum dhcp {
              description "Dynamically acquired via DHCP";
            }
          }
        }

        leaf interface {
          type if:interface-ref;
          description "Interface DNS server was learned from, if DHCP";
        }
      }

      container statistics {
        description "DNS resolver statistics";

        leaf cache-size {
          description "Current number of entries in DNS cache";
          type yang:counter32;
        }

        leaf cache-hits {
          description "Number of successful cache lookups";
          type yang:counter64;
        }

        leaf cache-misses {
          description "Number of failed cache lookups";
          type yang:counter64;
        }
      }
    }
  }

  deviation "/sys:system/sys:hostname" {
    deviate replace {
      type infix-sys:hostname;
    }
    description "Linux hostname can only be max 64 charachters long.";
  }

  deviation "/sys:system/sys:clock/sys:timezone/sys:timezone-name/sys:timezone-name" {
    deviate replace {
      type iana-tz:iana-timezone;
    }
  }

  deviation "/sys:system/sys:clock/sys:timezone/sys:timezone-utc-offset/sys:timezone-utc-offset" {
    description "Timezone UTC offset should be set in hours, not minutes";
    deviate replace {
      type int16 {
        range "-12 .. 14";
      }
      units "hours";
    }
  }

  deviation "/sys:system/sys:radius" {
    deviate not-supported;
  }

  deviation "/sys:system/sys:dns-resolver/sys:server/sys:transport/sys:udp-and-tcp/sys:udp-and-tcp/sys:port" {
    deviate not-supported;
  }

  deviation "/sys:system/sys:authentication/sys:user/sys:name" {
    deviate replace {
      type infix-sys:username;
    }
  }

  deviation "/sys:system/sys:authentication/sys:user/sys:password" {
    description "Extended password hash, including missing types, and $factory$.";
    deviate replace {
      type infix-sys:crypt-hash;
    }
  }
}
