Current File : //opt/support/lib/arg_types.py |
"""Custom argparse types"""
from pathlib import Path
import re
import string
from argparse import ArgumentTypeError as BadArg
import netaddr
import rads
CPMOVE_RE = re.compile(
r'(?:cpmove|backup)-' # prefix
r'(?:(?:[0-9.]{1,10}_)(?:[0-9-]{1,10}_))?' # optional date
r'((?![0-9])[0-9a-zA-Z]{1,20})\.tar\.gz$' # username and suffix
)
# Technically capital A-Z would be valid too, but for our purposes, reject that
DOMAIN_RE = re.compile(r'^(?:(?!-)[a-z0-9-]{1,63}(?<!-)\.)+[a-z]+$')
# (?: Start of group #1
# (?!-) Can't start with a hyphen
# [a-z0-9-]{1,63} Domain name is [a-z0-9-], 1 to 63 length
# (?<!-) Can't end with hyphen
# \. Follow by a dot "."
# )+ End of group #1; must appear at least once
# [a-z]+ TLD
# $ End
def cpuser_safe_arg(user: str) -> str:
"""Argparse type: checks rads.cpuser_safe"""
if not rads.cpuser_safe(user):
raise BadArg('user does not exist or is restricted')
return user
def any_cpuser_arg(user: str) -> str:
"""Accepts any cPanel user"""
if not rads.is_cpuser(user):
raise BadArg('not a valid cPanel user')
return user
def valid_domain(domain: str) -> str:
"""Argparse type: validates a domain"""
if not DOMAIN_RE.match(domain):
raise BadArg('invalid domain')
return domain
def valid_username(user: str) -> str:
"""Argparse type: validate a username as documented on docs.cpanel.net"""
if not user:
raise BadArg('cannot be blank')
valid_chars = string.ascii_lowercase + string.digits
if any(x for x in user if x not in valid_chars):
raise BadArg("may only use lower letters (a-z) and digits (0-9)")
if len(user) > 16:
raise BadArg('cannot contain more than 16 characters')
if user[0] in string.digits:
raise BadArg('cannot begin with a digit (0-9)')
if user.startswith('test'):
raise BadArg('cannot begin with the string test')
if user.endswith('assword'):
raise BadArg('cannot end with the string assword')
if user in rads.OUR_RESELLERS:
raise BadArg('restoring this user with this tool is forbidden')
return user
def cpmove_file_type(str_path: str) -> Path:
"""Argparse type: a cPanel backup within /home"""
path = existing_file(str_path)
if not path.is_relative_to('/home'):
raise BadArg('Backups should be in the /home directory')
if not CPMOVE_RE.match(path.name):
raise BadArg(f"{path} is not a cPanel backup")
return path
def existing_file(str_path: str) -> Path:
"""Argparse type: any existing file"""
path = Path(str_path).resolve()
if not path.is_file():
raise BadArg(f'{path} is not a file')
return path
def path_in_home(str_path: str) -> Path:
"""Argparse type: a path relative to /home"""
path = Path(str_path).resolve()
if not path.is_dir() or not path.is_relative_to('/home'):
raise BadArg(f'{path} is not a directory relative to /home')
return path
def ipaddress(addr_str: str) -> netaddr.IPAddress:
"""Argparse type: checks an IP address"""
try:
if netaddr.valid_ipv4(addr_str) or netaddr.valid_ipv6(addr_str):
addr = netaddr.IPAddress(addr_str)
else:
addr = None
except netaddr.AddrFormatError:
addr = None
if addr and (
addr.is_netmask() or addr.is_loopback() or addr.is_link_local()
):
addr = None
if addr:
return addr
raise BadArg('invalid IP Address')