# Copyright 2011 Gilt Groupe, INC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
mothership.validate

a library of validation methods
for various types of data
"""

# imports
import base64
import struct
import types
import re

# All of the models and sqlalchemy are brought in
# to simplify referencing
from mothership.mothership_models import *

class ValidationError(Exception):
    pass

# Validates realm input data
def v_realm(cfg, realm):
    """
    [description]
    validates realm input data

    [parameter info]
    required:
        cfg: the config object. useful everywhere
        realm: the realm we're trying to validate

    [return value]
    True/False based on success of validation
    """

    realm_set = set(cfg.realms)
    if realm in realm_set:
        return True
    else:
        print 'valid realms are:'
        print ' '.join(cfg.realms)
        return False

# Validates site_id input data
def v_site_id(cfg, site_id):
    """
    [description]
    validates site_id input data

    [parameter info]
    required:
        cfg: the config object. useful everywhere
        site_id: the site_id we're trying to validate

    [return value]
    True/False based on success of validation
    """

    site_id_set = set(cfg.site_ids)
    if site_id in site_id_set:
        return True
    else:
        print 'valid site_ids are:'
        print ' '.join(cfg.site_ids)
        return False


# Validates domain input data
def v_domain(cfg, domain):
    """
    [description]
    validates domain input data (probably not necessary)

    [parameter info]
    required:
        cfg: the config object. useful everywhere
        domain: the domain we're trying to validate

    [return value]
    True/False based on success of validation
    """

    if domain == cfg.domain:
        return True
    else:
        return False


# Validates ssh2 pubkeys
def v_ssh2_pubkey(cfg, key):
    """
    [description]
    validates ssh2 public keys

    [parameter info]
    required:
        cfg: the config object. useful everywhere
        key: the ssh2 public key we're trying to validate

    [return value]
    True/False based on success of validation
    """

    DSA_KEY_ID="ssh-dss"
    RSA_KEY_ID="ssh-rsa"

    if re.match(DSA_KEY_ID+'|'+RSA_KEY_ID, key):
        k = key.split(' ')
    else:
        return False

    if k:
        try:
            data = base64.decodestring(k[1])
        except IndexError:
            return False
        int_len = 4
        str_len = struct.unpack('>I', data[:int_len])[0] # this should return 7
        if DSA_KEY_ID in key:
          if data[int_len:int_len+str_len] == DSA_KEY_ID:
              return True
          else:
              return False
        else:
            if data[int_len:int_len+str_len] == RSA_KEY_ID:
              return True
            else:
              return False
    else:
       return False


# Validates UNIX uids
def v_uid(cfg, uid):
    """
    [description]
    validates UNIX UIDs

    [parameter info]
    required:
        cfg: the config object. useful everywhere
        uid: the UID we're trying to validate

    [return value]
    True/False based on success of validation
    """

    if type(uid) == types.IntType:
        if uid >= cfg.uid_start and uid <= cfg.uid_end:
            return True
        else:
            print "UID is outside the allowed range (%s to %s)" % (cfg.uid_start, cfg.uid_end)
            return False 
    elif uid == None:
        return False
    else:
        print "UID must be an integer!"
        return False

# Looks for a UID in the db and returns true if present, false if absent
def v_uid_in_db(cfg, uid, realm, site_id):
    """
    [description]
    looks for a UID in the db

    [parameter info]
    required:
        cfg: the config object. useful everywhere
        uid: the UID we're trying to find
        realm: the realm we're trying to find it in
        site_id: the site_id we're trying to find it in

    [return value]
    True/False based on success of validation
    """

    uidlist = []
    u = cfg.dbsess.query(Users).\
        filter(Users.realm==realm).\
        filter(Users.site_id==site_id).all()
    for userentry in u:
        uidlist.append(userentry.uid)
    uid_set = set(uidlist)
    if uid in uid_set:
        return True
    else:
        return False

# Looks for a GID in the db and returns true if present, false if absent
def v_gid_in_db(cfg, gid, realm, site_id):
    """
    [description]
    looks for a GID in the db

    [parameter info]
    required:
        cfg: the config object. useful everywhere
        gid: the GID we're looking for
        realm: the realm we're trying to find it in
        site_id: the site_id we're trying to find it in

    [return value]
    returns an integer representing the GID if the GID is in the db
    returns False if the GID is not in the db
    """

    gidlist = []
    g = cfg.dbsess.query(Groups).\
        filter(Groups.realm==realm).\
        filter(Groups.site_id==site_id).all()
    for groupentry in g:
        gidlist.append(groupentry.gid)
    gid_set = set(gidlist)
    if gid in gid_set:
        return True
    else:
        return False

# Validates UNIX gids
def v_gid(cfg, gid):
    """
    [description]
    validates UNIX GIDs

    [parameter info]
    required:
        cfg: the config object. useful everywhere
        gid: the GID we're trying to validate

    [return value]
    True/False based on success of validation
    """

    if type(gid) == types.IntType:
        if gid >= cfg.gid_start and gid <= cfg.gid_end:
            return True
        else:
            print "GID is outside the allowed range (%s to %s)" % (cfg.gid_start, cfg.gid_end)
            return False
    elif gid == None:
        return False
    else:
        print "GID must be an integer!"
        return False


# get an unqualified or fully-qualified name (host or user)
# for either servers or users
def v_get_fqn(cfg, name):
    """
    [description]
    get an unqualified or fully-qualified name (host or user) for either servers or users. depending on what we're supplied with it will either return a fqn or present the user with a menu to pick the unqn and then return a fqn

    [parameter info]
    required:
        cfg: the config object. useful everywhere
        name: the user/group name we're fetching the fqn for OR part of an unqualified name

    [return value]
    returns a fully-qualified user/group name if we're supplied with a name
    returns an un-qualified name if we're not supplied with a name
    """

    count = 1
    select = {}
    sub = name.split('.')
    # if we got a fully-qualified name/hostname
    if len(sub) == 5:
        n = sub[0]
        r = sub[1]
        s = sub[2]
        d = sub[3]+'.'+sub[4]
        # check to see if the domain is valid
        if not v_domain(cfg, d):
            raise ValidationError("invalid domain \"%s\", aborting" % d)
        # check to see if the site_id is valid
        if not v_site_id(cfg, s):
            raise ValidationError("invalid site_id \"%s\", aborting" % s)
        # check to see if the realm is valid
        if not v_realm(cfg, r):
            raise ValidationError("invalid realm \"%s\", aborting" % r)
        # if everything is valid, fire back name.realm.site_id.domain
        return n+'.'+r+'.'+s+'.'+d
    # if we got everything but the name
    elif len(sub) == 4:
        r = sub[0]
        s = sub[1]
        d = sub[2]+'.'+sub[3]
        # check to see if the domain is valid
        if not v_domain(cfg, d):
            raise ValidationError("invalid domain \"%s\", aborting" % d)
        # check to see if the site_id is valid
        if not v_site_id(cfg, s):
            raise ValidationError("invalid site_id \"%s\", aborting" % s)
        # check to see if the realm is valid
        if not v_realm(cfg, r):
            raise ValidationError("invalid realm \"%s\", aborting" % r)
        # if everything is valid, fire back realm.site_id.domain
        return r+'.'+s+'.'+d
    # 3 items could be either site_id.domain.tld or name.realm.site_id
    # let's figure out which it is...
    elif len(sub) == 3:
        s = sub[0]
        d = sub[1]+'.'+sub[2]
        n = sub[0]