2009-12-28 03:04:33 +03:00
# Unix SMB/CIFS implementation.
2012-09-26 00:34:36 +04:00
# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2009-2012
2011-05-20 00:17:07 +04:00
# Copyright (C) Theresa Halloran <theresahalloran@gmail.com> 2011
2009-12-28 03:04:33 +03:00
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
2023-05-16 05:35:41 +03:00
import json
2018-07-30 09:21:38 +03:00
import optparse
2023-05-16 05:24:27 +03:00
import sys
import textwrap
import traceback
2018-07-30 09:21:38 +03:00
import samba
2023-05-16 05:24:27 +03:00
from ldb import ERR_INVALID_CREDENTIALS , LdbError
2018-04-19 05:15:25 +03:00
from samba import colour
2023-05-16 04:54:59 +03:00
from samba . auth import system_session
2022-09-01 06:32:07 +03:00
from samba . getopt import SambaOption , OptionError
2018-08-21 03:08:59 +03:00
from samba . logger import get_samba_logger
2023-05-16 04:54:59 +03:00
from samba . samdb import SamDB
2009-12-28 15:53:18 +03:00
2023-05-16 05:35:41 +03:00
from . encoders import JSONEncoder
2023-05-16 02:47:45 +03:00
from . validators import ValidationError
2018-07-30 09:20:39 +03:00
2019-06-21 04:12:01 +03:00
class Option ( SambaOption ) :
2023-05-16 02:47:45 +03:00
ATTRS = SambaOption . ATTRS + [ " validators " ]
2019-03-12 12:25:40 +03:00
SUPPRESS_HELP = optparse . SUPPRESS_HELP
2009-12-28 15:53:18 +03:00
2023-05-16 02:47:45 +03:00
def run_validators ( self , opt , value ) :
""" Runs the list of validators on the current option.
If the validator raises ValidationError , turn that into CommandError
which gives nicer output .
"""
validators = getattr ( self , " validators " ) or [ ]
for validator in validators :
try :
validator ( opt , value )
except ValidationError as e :
raise CommandError ( e )
def convert_value ( self , opt , value ) :
""" Override convert_value to run validators just after.
This can also be done in process ( ) but there we would have to
replace the entire method .
"""
value = super ( ) . convert_value ( opt , value )
self . run_validators ( opt , value )
return value
2011-11-02 18:33:12 +04:00
# This help formatter does text wrapping and preserves newlines
2018-07-30 09:20:39 +03:00
2011-11-02 18:33:12 +04:00
class PlainHelpFormatter ( optparse . IndentedHelpFormatter ) :
2018-07-30 09:19:05 +03:00
def format_description ( self , description = " " ) :
2018-07-30 09:13:57 +03:00
desc_width = self . width - self . current_indent
2018-07-30 09:18:25 +03:00
indent = " " * self . current_indent
2018-07-30 09:13:57 +03:00
paragraphs = description . split ( ' \n ' )
wrapped_paragraphs = [
textwrap . fill ( p ,
2018-07-30 09:16:12 +03:00
desc_width ,
initial_indent = indent ,
subsequent_indent = indent )
2018-07-30 09:13:57 +03:00
for p in paragraphs ]
result = " \n " . join ( wrapped_paragraphs ) + " \n "
return result
2011-11-02 18:33:12 +04:00
2012-10-08 14:45:20 +04:00
def format_epilog ( self , epilog ) :
if epilog :
return " \n " + epilog + " \n "
else :
return " "
2009-12-28 15:53:18 +03:00
2018-07-30 09:20:39 +03:00
2009-12-28 03:04:33 +03:00
class Command ( object ) :
2011-07-19 02:34:45 +04:00
""" A samba-tool command. """
2011-10-14 01:08:32 +04:00
def _get_short_description ( self ) :
2009-12-28 18:05:04 +03:00
return self . __doc__ . splitlines ( ) [ 0 ] . rstrip ( " \n " )
2009-12-28 15:53:18 +03:00
2011-10-14 01:08:32 +04:00
short_description = property ( _get_short_description )
def _get_full_description ( self ) :
lines = self . __doc__ . split ( " \n " )
return lines [ 0 ] + " \n " + textwrap . dedent ( " \n " . join ( lines [ 1 : ] ) )
2009-12-28 15:53:18 +03:00
2011-10-14 01:08:32 +04:00
full_description = property ( _get_full_description )
2011-10-14 01:16:58 +04:00
def _get_name ( self ) :
name = self . __class__ . __name__
if name . startswith ( " cmd_ " ) :
return name [ 4 : ]
return name
name = property ( _get_name )
2011-10-14 01:08:32 +04:00
# synopsis must be defined in all subclasses in order to provide the
# command usage
synopsis = None
2011-07-28 22:21:40 +04:00
takes_args = [ ]
takes_options = [ ]
2012-02-06 19:33:38 +04:00
takes_optiongroups = { }
2011-11-02 19:39:47 +04:00
2012-09-26 00:34:36 +04:00
hidden = False
2022-09-09 05:48:29 +03:00
use_colour = True
2022-09-09 05:38:18 +03:00
requested_colour = None
2012-09-26 00:34:36 +04:00
2012-09-10 16:02:19 +04:00
raw_argv = None
raw_args = None
raw_kwargs = None
2022-09-07 07:33:33 +03:00
def _set_files ( self , outf = None , errf = None ) :
if outf is not None :
self . outf = outf
if errf is not None :
self . errf = errf
2011-11-02 19:39:47 +04:00
def __init__ ( self , outf = sys . stdout , errf = sys . stderr ) :
2022-09-07 07:33:33 +03:00
self . _set_files ( outf , errf )
2009-12-28 03:04:33 +03:00
2018-10-26 10:20:55 +03:00
def usage ( self , prog = None ) :
2011-10-14 01:27:22 +04:00
parser , _ = self . _create_parser ( prog )
2009-12-28 18:48:07 +03:00
parser . print_usage ( )
2009-12-28 03:21:27 +03:00
2022-09-09 06:08:30 +03:00
def _print_error ( self , msg , evalue = None , klass = None ) :
err = colour . c_DARK_RED ( " ERROR " )
klass = ' ' if klass is None else f ' ( { klass } ) '
if evalue is None :
print ( f " { err } { klass } : { msg } " , file = self . errf )
else :
print ( f " { err } { klass } : { msg } - { evalue } " , file = self . errf )
2023-05-16 04:54:59 +03:00
def ldb_connect ( self , ldap_url , sambaopts , credopts ) :
""" Helper to connect to Ldb database using command line opts. """
lp = sambaopts . get_loadparm ( )
creds = credopts . get_credentials ( lp )
return SamDB ( ldap_url , credentials = creds ,
session_info = system_session ( lp ) , lp = lp )
2023-05-16 05:35:41 +03:00
def print_json ( self , data ) :
""" Print json on the screen using consistent formatting and sorting.
A custom JSONEncoder class is used to help with serializing unknown
objects such as Dn for example .
"""
json . dump ( data , self . outf , cls = JSONEncoder , indent = 2 , sort_keys = True )
self . outf . write ( " \n " )
2010-11-29 06:11:57 +03:00
def show_command_error ( self , e ) :
2023-05-16 04:39:12 +03:00
""" display a command error """
2010-11-29 06:11:57 +03:00
if isinstance ( e , CommandError ) :
( etype , evalue , etraceback ) = e . exception_info
inner_exception = e . inner_exception
message = e . message
force_traceback = False
else :
( etype , evalue , etraceback ) = sys . exc_info ( )
inner_exception = e
message = " uncaught exception "
force_traceback = True
2022-09-01 06:32:07 +03:00
if isinstance ( e , OptionError ) :
print ( evalue , file = self . errf )
self . usage ( )
force_traceback = False
elif isinstance ( inner_exception , LdbError ) :
2018-05-04 13:28:46 +03:00
( ldb_ecode , ldb_emsg ) = inner_exception . args
samba-tool: reduce repetitious jargon on credentials failure
We already print the following due to DBG_ERR()s:
cli_credentials_failed_kerberos_login: krb5_cc_get_principal failed: No such file or directory
Failed to bind - LDAP error 49 LDAP_INVALID_CREDENTIALS - <8009030C: LdapErr: DSID-0C0904DC, comment: AcceptSecurityContext error, data 52e, v1db1> <>
Failed to connect to 'ldap://10.53.57.30' with backend 'ldap': LDAP error 49 LDAP_INVALID_CREDENTIALS - <8009030C: LdapErr: DSID-0C0904DC, comment: AcceptSecurityContext error, data 52e, v1db1> <>
We don't *really* need to follow that with:
ERROR(ldb): LDAP connection to ldap://10.53.57.30 failed - LDAP error 49 LDAP_INVALID_CREDENTIALS - <8009030C: LdapErr: DSID-0C0904DC, comment: AcceptSecurityContext error, data 52e, v1db1> <>
rather we can say:
Bad username or password.
Also, we don't really need to print a traceback, which we seem to do
for some commands and not others.
Maybe *sometimes* "bad username or password" might be technically
incorrect (e.g. --simple-bind-dn), but in those cases the user is
already behaving strangely, and they will still see the
LDAP_INVALID_CREDENTIALS twice. Kerberos failures don't come this way.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=9608
Signed-off-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
2022-08-19 01:12:07 +03:00
if ldb_ecode == ERR_INVALID_CREDENTIALS :
print ( " Invalid username or password " , file = self . errf )
force_traceback = False
2022-08-19 08:06:48 +03:00
elif ldb_emsg == ' LDAP client internal error: NT_STATUS_NETWORK_UNREACHABLE ' :
print ( " Could not reach remote server " , file = self . errf )
force_traceback = False
2022-09-09 07:13:12 +03:00
elif ldb_emsg . startswith ( " Unable to open tdb " ) :
self . _print_error ( message , ldb_emsg , ' ldb ' )
force_traceback = False
samba-tool: reduce repetitious jargon on credentials failure
We already print the following due to DBG_ERR()s:
cli_credentials_failed_kerberos_login: krb5_cc_get_principal failed: No such file or directory
Failed to bind - LDAP error 49 LDAP_INVALID_CREDENTIALS - <8009030C: LdapErr: DSID-0C0904DC, comment: AcceptSecurityContext error, data 52e, v1db1> <>
Failed to connect to 'ldap://10.53.57.30' with backend 'ldap': LDAP error 49 LDAP_INVALID_CREDENTIALS - <8009030C: LdapErr: DSID-0C0904DC, comment: AcceptSecurityContext error, data 52e, v1db1> <>
We don't *really* need to follow that with:
ERROR(ldb): LDAP connection to ldap://10.53.57.30 failed - LDAP error 49 LDAP_INVALID_CREDENTIALS - <8009030C: LdapErr: DSID-0C0904DC, comment: AcceptSecurityContext error, data 52e, v1db1> <>
rather we can say:
Bad username or password.
Also, we don't really need to print a traceback, which we seem to do
for some commands and not others.
Maybe *sometimes* "bad username or password" might be technically
incorrect (e.g. --simple-bind-dn), but in those cases the user is
already behaving strangely, and they will still see the
LDAP_INVALID_CREDENTIALS twice. Kerberos failures don't come this way.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=9608
Signed-off-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
2022-08-19 01:12:07 +03:00
else :
2022-09-09 06:08:30 +03:00
self . _print_error ( message , ldb_emsg , ' ldb ' )
2022-09-09 07:13:12 +03:00
2010-11-29 06:11:57 +03:00
elif isinstance ( inner_exception , AssertionError ) :
2022-09-09 06:08:30 +03:00
self . _print_error ( message , klass = ' assert ' )
2010-11-29 06:11:57 +03:00
force_traceback = True
elif isinstance ( inner_exception , RuntimeError ) :
2022-09-09 06:08:30 +03:00
self . _print_error ( message , evalue , ' runtime ' )
2010-11-29 06:11:57 +03:00
elif type ( inner_exception ) is Exception :
2022-09-09 06:08:30 +03:00
self . _print_error ( message , evalue , ' exception ' )
2010-11-29 06:11:57 +03:00
force_traceback = True
elif inner_exception is None :
2022-09-09 06:08:30 +03:00
self . _print_error ( message )
2010-11-29 06:11:57 +03:00
else :
2022-09-09 06:08:30 +03:00
self . _print_error ( message , evalue , str ( etype ) )
2010-11-29 06:11:57 +03:00
if force_traceback or samba . get_debug_level ( ) > = 3 :
2016-11-28 04:30:43 +03:00
traceback . print_tb ( etraceback , file = self . errf )
2010-11-29 06:11:57 +03:00
2018-10-26 10:20:55 +03:00
def _create_parser ( self , prog = None , epilog = None ) :
2011-10-14 01:27:22 +04:00
parser = optparse . OptionParser (
usage = self . synopsis ,
description = self . full_description ,
2011-11-02 18:33:12 +04:00
formatter = PlainHelpFormatter ( ) ,
2018-07-30 09:19:05 +03:00
prog = prog , epilog = epilog )
2009-12-28 18:48:07 +03:00
parser . add_options ( self . takes_options )
optiongroups = { }
2018-10-17 20:06:34 +03:00
for name in sorted ( self . takes_optiongroups . keys ( ) ) :
optiongroup = self . takes_optiongroups [ name ]
2009-12-28 18:48:07 +03:00
optiongroups [ name ] = optiongroup ( parser )
parser . add_option_group ( optiongroups [ name ] )
2022-09-09 05:48:29 +03:00
if self . use_colour :
parser . add_option ( " --color " ,
help = " use colour if available (default: auto) " ,
metavar = " always|never|auto " ,
default = " auto " )
2009-12-28 18:48:07 +03:00
return parser , optiongroups
2009-12-28 18:05:04 +03:00
def message ( self , text ) :
2018-07-30 09:18:25 +03:00
self . outf . write ( text + " \n " )
2009-12-28 15:53:18 +03:00
2022-09-07 07:33:33 +03:00
def _resolve ( self , path , * argv , outf = None , errf = None ) :
2022-09-07 06:34:23 +03:00
""" This is a leaf node, the command that will actually run. """
2022-09-07 07:33:33 +03:00
self . _set_files ( outf , errf )
2022-09-07 06:34:23 +03:00
self . command_name = path
return ( self , argv )
2009-12-28 15:53:18 +03:00
def _run ( self , * argv ) :
2022-09-07 06:34:23 +03:00
parser , optiongroups = self . _create_parser ( self . command_name )
2022-09-07 06:07:43 +03:00
opts , args = parser . parse_args ( list ( argv ) )
2009-12-28 18:48:07 +03:00
# Filter out options from option groups
kwargs = dict ( opts . __dict__ )
for option_group in parser . option_groups :
for option in option_group . option_list :
2010-04-09 04:37:20 +04:00
if option . dest is not None :
del kwargs [ option . dest ]
2009-12-28 18:48:07 +03:00
kwargs . update ( optiongroups )
2011-07-19 00:48:03 +04:00
2022-09-09 05:48:29 +03:00
if self . use_colour :
self . apply_colour_choice ( kwargs . pop ( ' color ' , ' auto ' ) )
2011-07-19 00:48:03 +04:00
# Check for a min a max number of allowed arguments, whenever possible
2023-06-06 14:17:58 +03:00
# The suffix "?" means zero or one occurrence
# The suffix "+" means at least one occurrence
# The suffix "*" means zero or more occurrences
2009-12-30 22:40:11 +03:00
min_args = 0
max_args = 0
2011-07-19 00:48:03 +04:00
undetermined_max_args = False
2009-12-30 21:53:05 +03:00
for i , arg in enumerate ( self . takes_args ) :
2016-02-25 02:42:09 +03:00
if arg [ - 1 ] != " ? " and arg [ - 1 ] != " * " :
2018-07-30 09:13:57 +03:00
min_args + = 1
2016-02-25 02:42:09 +03:00
if arg [ - 1 ] == " + " or arg [ - 1 ] == " * " :
2018-07-30 09:13:57 +03:00
undetermined_max_args = True
2011-07-19 00:48:03 +04:00
else :
2018-07-30 09:13:57 +03:00
max_args + = 1
2012-09-27 20:30:47 +04:00
if ( len ( args ) < min_args ) or ( not undetermined_max_args and len ( args ) > max_args ) :
2011-07-19 00:48:03 +04:00
parser . print_usage ( )
2009-12-28 18:48:07 +03:00
return - 1
2011-07-19 00:48:03 +04:00
2012-09-10 16:02:19 +04:00
self . raw_argv = list ( argv )
self . raw_args = args
self . raw_kwargs = kwargs
2009-12-28 22:37:48 +03:00
try :
return self . run ( * args , * * kwargs )
2018-02-14 00:07:23 +03:00
except Exception as e :
2010-11-29 06:11:57 +03:00
self . show_command_error ( e )
2009-12-28 22:37:48 +03:00
return - 1
2009-12-28 15:53:18 +03:00
2022-08-16 04:43:54 +03:00
def run ( self , * args , * * kwargs ) :
2017-02-17 22:47:12 +03:00
""" Run the command. This should be overridden by all subclasses. """
2022-08-16 04:43:54 +03:00
raise NotImplementedError ( f " ' { self . command_name } ' run method not implemented " )
2009-12-28 03:04:33 +03:00
2018-08-21 03:08:59 +03:00
def get_logger ( self , name = " " , verbose = False , quiet = False , * * kwargs ) :
2011-10-13 01:21:52 +04:00
""" Get a logger object. """
2018-08-21 03:08:59 +03:00
return get_samba_logger (
name = name or self . name , stream = self . errf ,
verbose = verbose , quiet = quiet ,
* * kwargs )
2009-12-28 03:04:33 +03:00
2018-04-19 05:15:25 +03:00
def apply_colour_choice ( self , requested ) :
""" Heuristics to work out whether the user wants colour output, from a
- - color = yes | no | auto option . This alters the ANSI 16 bit colour
" constants " in the colour module to be either real colours or empty
strings .
"""
2022-09-09 05:38:18 +03:00
self . requested_colour = requested
2021-07-07 01:43:59 +03:00
try :
2022-09-09 06:24:29 +03:00
colour . colour_if_wanted ( self . outf ,
self . errf ,
hint = requested )
2021-07-07 01:43:59 +03:00
except ValueError as e :
raise CommandError ( f " Unknown --color option: { requested } "
" please choose from always|never|auto " )
2018-04-19 05:15:25 +03:00
2011-07-18 19:30:23 +04:00
2009-12-28 15:53:18 +03:00
class SuperCommand ( Command ) :
2011-07-19 02:34:45 +04:00
""" A samba-tool command with subcommands. """
2009-12-28 15:53:18 +03:00
2011-10-14 01:47:45 +04:00
synopsis = " % prog <subcommand> "
2009-12-28 15:53:18 +03:00
subcommands = { }
2022-09-07 07:33:33 +03:00
def _resolve ( self , path , * args , outf = None , errf = None ) :
2022-09-07 06:34:23 +03:00
""" This is an internal node. We need to consume one of the args and
find the relevant child , returning an instance of that Command .
If there are no children , this SuperCommand will be returned
and its _run ( ) will do a - - help like thing .
"""
self . command_name = path
2022-09-07 07:33:33 +03:00
self . _set_files ( outf , errf )
2022-09-07 06:34:23 +03:00
# We collect up certain option arguments and pass them to the
# leaf, which is why we iterate over args, though we really
# expect to return in the first iteration.
deferred_args = [ ]
for i , a in enumerate ( args ) :
if a in self . subcommands :
sub_args = args [ i + 1 : ] + tuple ( deferred_args )
sub_path = f ' { path } { a } '
sub = self . subcommands [ a ]
2022-09-07 07:33:33 +03:00
return sub . _resolve ( sub_path , * sub_args , outf = outf , errf = errf )
2017-08-11 07:39:33 +03:00
2022-09-07 06:34:23 +03:00
elif a in [ ' --help ' , ' help ' , None , ' -h ' , ' -V ' , ' --version ' ] :
# we pass these to the leaf node.
if a == ' help ' :
a = ' --help '
deferred_args . append ( a )
continue
# they are talking nonsense
print ( " %s : no such subcommand: %s \n " % ( path , a ) , file = self . outf )
return ( self , [ ] )
# We didn't find a subcommand, but maybe we found e.g. --version
print ( " %s : missing subcommand \n " % ( path ) , file = self . outf )
return ( self , deferred_args )
def _run ( self , * argv ) :
2012-10-08 14:47:47 +04:00
epilog = " \n Available subcommands: \n "
2022-09-07 06:41:17 +03:00
subcmds = sorted ( self . subcommands . keys ( ) )
2011-10-13 02:36:44 +04:00
max_length = max ( [ len ( c ) for c in subcmds ] )
2012-09-26 00:34:36 +04:00
for cmd_name in subcmds :
cmd = self . subcommands [ cmd_name ]
2022-09-07 06:34:23 +03:00
if cmd . hidden :
continue
epilog + = " %*s - %s \n " % (
- max_length , cmd_name , cmd . short_description )
epilog + = ( " For more help on a specific subcommand, please type: "
f " { self . command_name } <subcommand> (-h|--help) \n " )
parser , optiongroups = self . _create_parser ( self . command_name , epilog = epilog )
2022-09-07 06:07:43 +03:00
opts , args = parser . parse_args ( list ( argv ) )
2012-10-08 14:47:47 +04:00
2022-09-07 06:41:17 +03:00
# note: if argv had --help, parser.parse_args() will have
# already done the .print_help() and attempted to exit with
# return code 0, so we won't get here.
2012-10-08 14:47:47 +04:00
parser . print_help ( )
return - 1
2009-12-30 23:06:21 +03:00
2009-12-28 15:53:18 +03:00
2009-12-28 18:05:04 +03:00
class CommandError ( Exception ) :
2011-10-14 01:27:22 +04:00
""" An exception class for samba-tool Command errors. """
2010-11-29 06:11:57 +03:00
def __init__ ( self , message , inner_exception = None ) :
self . message = message
self . inner_exception = inner_exception
self . exception_info = sys . exc_info ( )
2018-01-24 21:14:53 +03:00
def __repr__ ( self ) :
return " CommandError( %s ) " % self . message