1#!/usr/local/bin/python3.8
2# -*- coding: utf-8 -*-
3#
4# (c) Copyright @ 2015 HP Development Company, L.P.
5#
6# This program is free software; you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation; either version 2 of the License, or
9# (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program; if not, write to the Free Software
18# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
19#
20# Author: Amarnath Chitumalla
21#
22import os
23import getpass
24import time
25import string
26
27from . import utils, tui
28from .g import *
29from .sixext import BytesIO, StringIO
30from .sixext.moves import input
31from . import pexpect
32
33PASSWORD_RETRY_COUNT = 3
34
35AUTH_TYPES = {'mepis': 'su',
36              'debian': 'su',
37              'suse': 'su',
38              'mandriva': 'su',
39              'fedora': 'su',
40              'fedora28': 'sudo',
41              'redhat': 'su',
42              'rhel': 'su',
43              'slackware': 'su',
44              'gentoo': 'su',
45              'redflag': 'su',
46              'ubuntu': 'sudo',
47              'xandros': 'su',
48              'freebsd': 'su',
49              'linspire': 'su',
50              'ark': 'su',
51              'pclinuxos': 'su',
52              'centos': 'su',
53              'igos': 'su',
54              'linuxmint': 'sudo',
55              'linpus': 'sudo',
56              'gos': 'sudo',
57              'boss': 'su',
58              'lfs': 'su',
59              'manjarolinux': 'sudo',
60              }
61
62
63# This function promts for the username and password and returns
64# (username,password)
65def showPasswordPrompt(prompt):
66    import getpass
67    print ("")
68    print ("")
69    print (log.bold(prompt))
70    username = input("Username: ")
71    password = getpass.getpass("Password: ")
72
73    return (username, password)
74
75
76# TBD this function shoud be removed once distro class implemented
77def get_distro_name():
78    os_name = None
79    try:
80        import platform
81    except ImportError:
82        os_name = None
83
84    try:
85        os_name = platform.dist()[0]
86    except AttributeError:
87        import distro
88        os_name = distro.linux_distribution()[0]
89
90    if not os_name:
91        name = os.popen('lsb_release -i | cut -f 2')
92        os_name = name.read().strip()
93        name.close()
94
95    if not os_name:
96        name = os.popen("cat /etc/issue | awk '{print $1}' | head -n 1")
97        os_name = name.read().strip()
98        name.close()
99
100    os_name = os_name.lower()
101    if "redhatenterprise" in os_name:
102        os_name = 'rhel'
103    elif "suse" in os_name:
104        os_name = 'suse'
105    elif "arch" in os_name:
106         os_name = 'manjarolinux'
107
108    return os_name
109
110
111class Password(object):
112
113    def __init__(self, Mode=INTERACTIVE_MODE):
114        self.__password = ""
115        self.__password_prompt_str = ""
116        self.__passwordValidated = False
117        self.__mode = Mode
118        self.__readAuthType()  # self.__authType
119        self.__expectList = []
120
121        if not utils.to_bool(sys_conf.get('configure', 'qt5', '0')) and not not utils.to_bool(sys_conf.get('configure', 'qt4', '0')) and utils.to_bool(sys_conf.get('configure', 'qt3', '0')):
122            self.__ui_toolkit = 'qt3'
123        elif not utils.to_bool(sys_conf.get('configure', 'qt5', '0')) and not utils.to_bool(sys_conf.get('configure', 'qt3', '0')) and utils.to_bool(sys_conf.get('configure', 'qt4', '0')):
124            self.__ui_toolkit = 'qt4'
125        elif not utils.to_bool(sys_conf.get('configure', 'qt3', '0')) and not utils.to_bool(sys_conf.get('configure', 'qt4', '0')) and utils.to_bool(sys_conf.get('configure', 'qt5', '0')):
126            self.__ui_toolkit = 'qt5'
127
128        for s in utils.EXPECT_WORD_LIST:
129            try:
130                p = re.compile(s, re.I)
131            except TypeError:
132                self.__expectList.append(s)
133            else:
134                self.__expectList.append(p)
135
136    ##################### Private functions ######################
137
138    def __readAuthType(self):
139        # TBD: Getting distro name should get distro class
140        # added replace() to remove the spaces in distro_name
141        distro_name = get_distro_name().lower().replace(" ","")
142
143        self.__authType = user_conf.get('authentication', 'su_sudo', '')
144        if self.__authType != "su" and self.__authType != "sudo":
145            try:
146                self.__authType = AUTH_TYPES[distro_name]
147                if distro_name == 'fedora':
148                    import platform
149                    try:
150                       ver = int(platform.dist()[1])
151                    except AttributeError:
152                       import distro
153                       ver = int(distro.linux_distribution()[1])
154                    if ver >= 28:
155                       self.__authType = AUTH_TYPES['fedora28']
156            except KeyError:
157                log.warn("%s distro is not found in AUTH_TYPES" % distro_name)
158                self.__authType = 'su'
159
160    def __getPasswordDisplayString(self):
161        if self.__authType == "su":
162            return "Please enter the root/superuser password: "
163        else:
164            return "Please enter the sudoer (%s)'s password: " % os.getenv('USER')
165
166    def __changeAuthType(self):
167        if self.__authType == "sudo":
168            self.__authType = "su"
169        else:
170            self.__authType = "sudo"
171        user_conf.set('authentication', 'su_sudo', self.__authType)
172
173    def __get_password(self, pswd_msg=''):
174        if pswd_msg == '':
175            if self.__authType == "su":
176                pswd_msg = "Please enter the root/superuser password: "
177            else:
178                pswd_msg = "Please enter the sudoer (%s)'s password: " % os.getenv(
179                    'USER')
180        return getpass.getpass(log.bold(pswd_msg))
181
182    def __get_password_ui(self, pswd_msg='', user="root"):
183        if pswd_msg == '':
184            pswd_msg = "Your HP Device requires to install HP proprietary plugin\nPlease enter root/superuser password to continue"
185
186        if self.__ui_toolkit == "qt3":
187            from ui.setupform import showPasswordUI
188            username, password = showPasswordUI(pswd_msg, user, False)
189        elif self.__ui_toolkit == "qt5":
190            from ui5.setupdialog import showPasswordUI
191            username, password = showPasswordUI(pswd_msg, user, False)
192        else:  # self.__ui_toolkit == "qt4" --> default qt4
193            from ui4.setupdialog import showPasswordUI
194            username, password = showPasswordUI(pswd_msg, user, False)
195
196        if username == "" and password == "":
197            raise Exception("User Cancel")
198
199        return password
200
201    def __password_check(self, cmd, timeout=10):
202        import io
203        output = io.StringIO()
204        ok, ret = False, ''
205
206        try:
207            child = pexpect.spawnu(cmd, timeout=timeout)
208        except pexpect.ExceptionPexpect:
209            return 1, ''
210
211        try:
212            try:
213                start = time.time()
214
215                while True:
216                    update_spinner()
217
218                    i = child.expect(self.__expectList)
219
220                    cb = child.before
221                    if cb:
222                        start = time.time()
223                        output.write(cb)
224
225                    if i == 0:  # EOF
226                        ok, ret = True, output.getvalue()
227                        break
228
229                    elif i == 1:  # TIMEOUT
230                        if('true' in cmd and self.__password_prompt_str == ""):  # sudo true or su -c "true"
231                            cb = cb.replace("[", "\[")
232                            cb = cb.replace("]", "\]")
233
234                            self.__password_prompt_str = cb
235                            try:
236                                p = re.compile(cb, re.I)
237                            except TypeError:
238                                self.__expectList.append(cb)
239                            else:
240                                self.__expectList.append(p)
241                            log.debug(
242                                "Adding missing password prompt string [%s]" % self.__password_prompt_str)
243                        continue
244
245                    else:  # password
246                        if(self.__password_prompt_str == ""):
247                            self.__password_prompt_str = utils.EXPECT_WORD_LIST[
248                                i]
249                            log.debug(
250                                "Updating password prompt string [%s]" % self.__password_prompt_str)
251
252                        child.sendline(self.__password)
253
254            except (Exception, pexpect.ExceptionPexpect) as e:
255                log.exception()
256
257        finally:
258            cleanup_spinner()
259
260            try:
261                child.close()
262            except OSError:
263                pass
264
265        if ok:
266            return child.exitstatus, ret
267        else:
268
269            return 1, ''
270
271    def __validatePassword(self, pswd_msg):
272        x = 1
273        while True:
274            if self.__mode == INTERACTIVE_MODE:
275                self.__password = self.__get_password(pswd_msg)
276            else:
277                try:
278                    if self.getAuthType() == 'su':
279                        self.__password = self.__get_password_ui(
280                            pswd_msg, "root")
281                    else:
282                        self.__password = self.__get_password_ui(
283                            pswd_msg, os.getenv("USER"))
284                except Exception as ex:
285                    log.debug(ex)
286                    break
287
288            cmd = self.getAuthCmd() % "true"
289            log.debug(cmd)
290
291            status, output = self.__password_check(cmd)
292            log.debug("status = %s  output=%s " % (status, output))
293
294            if self.__mode == GUI_MODE:
295                if self.__ui_toolkit == "qt4":
296                    from ui4.setupdialog import FailureMessageUI
297                elif self.__ui_toolkit == "qt5":
298                    from ui5.setupdialog import FailureMessageUI
299                elif self.__ui_toolkit == "qt3":
300                    from ui.setupform import FailureMessageUI
301
302            if status == 0:
303                self.__passwordValidated = True
304                break
305            elif "not in the sudoers file" in output:
306                # TBD.. IF user doesn't have sudo permissions, needs to change
307                # to "su" type and query for password
308                self.__changeAuthType()
309                msg = "User doesn't have sudo permissions.\nChanging Authentication Type. Try again."
310                if self.__mode == GUI_MODE:
311                    FailureMessageUI(msg)
312                else:
313                    log.error(msg)
314                raise Exception("User is not in the sudoers file.")
315
316            else:
317                self.__password = ""
318                x += 1
319                if self.__mode == GUI_MODE:
320                    if x > PASSWORD_RETRY_COUNT:
321                        FailureMessageUI("Password incorrect. ")
322                        return
323                    else:
324                        FailureMessageUI("Password incorrect. %d attempt(s) left." % (
325                            PASSWORD_RETRY_COUNT + 1 - x))
326                else:
327                    if x > PASSWORD_RETRY_COUNT:
328                        log.error("Password incorrect. ")
329                        return
330                    else:
331                        log.error("Password incorrect. %d attempt(s) left." % (
332                            PASSWORD_RETRY_COUNT + 1 - x))
333
334    def __get_password_utils(self):
335        if self.__authType == "su":
336            AuthType, AuthCmd = 'su', 'su -c "%s"'
337        else:
338            AuthType, AuthCmd = 'sudo', 'sudo %s'
339
340        return AuthType, AuthCmd
341
342    def __get_password_utils_ui(self):
343        distro_name = get_distro_name().lower()
344        if self.__authType == "sudo":
345            AuthType, AuthCmd = 'sudo', 'sudo %s'
346        else:
347            AuthType, AuthCmd = 'su', 'su -c "%s"'
348        '''
349        if utils.which('kdesu'):
350            AuthType, AuthCmd = 'kdesu', 'kdesu -- %s'
351        elif utils.which('kdesudo'):
352            AuthType, AuthCmd = 'kdesudo', 'kdesudo -- %s'
353        elif utils.which('gnomesu'):
354            AuthType, AuthCmd = 'gnomesu', 'gnomesu -c "%s"'
355        elif utils.which('gksu'):
356            AuthType, AuthCmd = 'gksu' , 'gksu "%s"'
357        '''
358
359        return AuthType, AuthCmd
360
361    ##################### Public functions ######################
362
363    def clearPassword(self):
364        log.debug("Clearing password...")
365        self.__password = ""
366        self.__passwordValidated = False
367        if self.__authType == 'sudo':
368            utils.run("sudo -K")
369
370    def getAuthType(self):
371        if self.__mode == INTERACTIVE_MODE:
372            retValue = self.__authType
373        else:
374            retValue, AuthCmd = self.__get_password_utils_ui()
375
376        return retValue
377
378    def getAuthCmd(self):
379        if self.__mode == INTERACTIVE_MODE:
380            AuthType, AuthCmd = self.__get_password_utils()
381        else:
382            AuthType, AuthCmd = self.__get_password_utils_ui()
383
384        return AuthCmd
385
386    def getPassword(self, pswd_msg='', psswd_queried_cnt=0):
387        if self.__passwordValidated:
388            return self.__password
389
390        if psswd_queried_cnt:
391            return self.__password
392
393        self.__validatePassword(pswd_msg)
394        return self.__password
395
396    def getPasswordPromptString(self):
397        return self.__password_prompt_str
398