mirror of
https://gitlab.com/apparmor/apparmor
synced 2025-08-22 10:07:12 +00:00
the utility python modules to be used inside another tool with another textdomain binding and still have the translations for that tool and the stuff internal to the apparmor module convert properly.
467 lines
15 KiB
Python
467 lines
15 KiB
Python
# ----------------------------------------------------------------------
|
|
# Copyright (C) 2013 Kshitij Gupta <kgupta8592@gmail.com>
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of version 2 of the GNU General Public
|
|
# License as published by the Free Software Foundation.
|
|
#
|
|
# 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.
|
|
#
|
|
# ----------------------------------------------------------------------
|
|
import gettext
|
|
import sys
|
|
import os
|
|
import re
|
|
from apparmor.yasti import yastLog, SendDataToYast, GetDataFromYast
|
|
|
|
from apparmor.common import readkey, AppArmorException, DebugLogger, msg, TRANSLATION_DOMAIN
|
|
|
|
# setup module translations
|
|
t = gettext.translation(TRANSLATION_DOMAIN, fallback=True)
|
|
_ = t.gettext
|
|
|
|
# Set up UI logger for separate messages from UI module
|
|
debug_logger = DebugLogger('UI')
|
|
|
|
# The operating mode: yast or text, text by default
|
|
UI_mode = 'text'
|
|
|
|
ARROWS = {'A': 'UP', 'B': 'DOWN', 'C': 'RIGHT', 'D': 'LEFT'}
|
|
|
|
def getkey():
|
|
key = readkey()
|
|
if key == '\x1B':
|
|
key = readkey()
|
|
if key == '[':
|
|
key = readkey()
|
|
if(ARROWS.get(key, False)):
|
|
key = ARROWS[key]
|
|
return key.strip()
|
|
|
|
def UI_Info(text):
|
|
debug_logger.info(text)
|
|
if UI_mode == 'text':
|
|
sys.stdout.write(text + '\n')
|
|
else:
|
|
yastLog(text)
|
|
|
|
def UI_Important(text):
|
|
debug_logger.debug(text)
|
|
if UI_mode == 'text':
|
|
sys.stdout.write('\n' + text + '\n')
|
|
else:
|
|
SendDataToYast({
|
|
'type': 'dialog-error',
|
|
'message': text
|
|
})
|
|
path, yarg = GetDataFromYast()
|
|
|
|
def get_translated_hotkey(translated, cmsg=''):
|
|
msg = 'PromptUser: '+_('Invalid hotkey for')
|
|
|
|
# Originally (\S) was used but with translations it would not work :(
|
|
if re.search('\((\S+)\)', translated, re.LOCALE):
|
|
return re.search('\((\S+)\)', translated, re.LOCALE).groups()[0]
|
|
else:
|
|
if cmsg:
|
|
raise AppArmorException(cmsg)
|
|
else:
|
|
raise AppArmorException('%s %s' %(msg, translated))
|
|
|
|
def UI_YesNo(text, default):
|
|
debug_logger.debug('UI_YesNo: %s: %s %s' %(UI_mode, text, default))
|
|
default = default.lower()
|
|
ans = None
|
|
if UI_mode == 'text':
|
|
yes = _('(Y)es')
|
|
no = _('(N)o')
|
|
yeskey = get_translated_hotkey(yes).lower()
|
|
nokey = get_translated_hotkey(no).lower()
|
|
ans = 'XXXINVALIDXXX'
|
|
while ans not in ['y', 'n']:
|
|
sys.stdout.write('\n' + text + '\n')
|
|
if default == 'y':
|
|
sys.stdout.write('\n[%s] / %s\n' % (yes, no))
|
|
else:
|
|
sys.stdout.write('\n%s / [%s]\n' % (yes, no))
|
|
ans = getkey()
|
|
if ans:
|
|
# Get back to english from localised answer
|
|
ans = ans.lower()
|
|
if ans == yeskey:
|
|
ans = 'y'
|
|
elif ans == nokey:
|
|
ans = 'n'
|
|
elif ans == 'left':
|
|
default = 'y'
|
|
elif ans == 'right':
|
|
default = 'n'
|
|
else:
|
|
ans = 'XXXINVALIDXXX'
|
|
continue # If user presses any other button ask again
|
|
else:
|
|
ans = default
|
|
|
|
else:
|
|
SendDataToYast({
|
|
'type': 'dialog-yesno',
|
|
'question': text
|
|
})
|
|
ypath, yarg = GetDataFromYast()
|
|
ans = yarg['answer']
|
|
if not ans:
|
|
ans = default
|
|
return ans
|
|
|
|
def UI_YesNoCancel(text, default):
|
|
debug_logger.debug('UI_YesNoCancel: %s: %s %s' % (UI_mode, text, default))
|
|
default = default.lower()
|
|
ans = None
|
|
if UI_mode == 'text':
|
|
yes = _('(Y)es')
|
|
no = _('(N)o')
|
|
cancel = _('(C)ancel')
|
|
|
|
yeskey = get_translated_hotkey(yes).lower()
|
|
nokey = get_translated_hotkey(no).lower()
|
|
cancelkey = get_translated_hotkey(cancel).lower()
|
|
|
|
ans = 'XXXINVALIDXXX'
|
|
while ans not in ['c', 'n', 'y']:
|
|
sys.stdout.write('\n' + text + '\n')
|
|
if default == 'y':
|
|
sys.stdout.write('\n[%s] / %s / %s\n' % (yes, no, cancel))
|
|
elif default == 'n':
|
|
sys.stdout.write('\n%s / [%s] / %s\n' % (yes, no, cancel))
|
|
else:
|
|
sys.stdout.write('\n%s / %s / [%s]\n' % (yes, no, cancel))
|
|
ans = getkey()
|
|
if ans:
|
|
# Get back to english from localised answer
|
|
ans = ans.lower()
|
|
if ans == yeskey:
|
|
ans = 'y'
|
|
elif ans == nokey:
|
|
ans = 'n'
|
|
elif ans == cancelkey:
|
|
ans= 'c'
|
|
elif ans == 'left':
|
|
if default == 'n':
|
|
default = 'y'
|
|
elif default == 'c':
|
|
default = 'n'
|
|
elif ans == 'right':
|
|
if default == 'y':
|
|
default = 'n'
|
|
elif default == 'n':
|
|
default = 'c'
|
|
else:
|
|
ans = default
|
|
else:
|
|
SendDataToYast({
|
|
'type': 'dialog-yesnocancel',
|
|
'question': text
|
|
})
|
|
ypath, yarg = GetDataFromYast()
|
|
ans = yarg['answer']
|
|
if not ans:
|
|
ans = default
|
|
return ans
|
|
|
|
def UI_GetString(text, default):
|
|
debug_logger.debug('UI_GetString: %s: %s %s' % (UI_mode, text, default))
|
|
string = default
|
|
if UI_mode == 'text':
|
|
sys.stdout.write('\n' + text)
|
|
string = sys.stdin.readline()
|
|
else:
|
|
SendDataToYast({
|
|
'type': 'dialog-getstring',
|
|
'label': text,
|
|
'default': default
|
|
})
|
|
ypath, yarg = GetDataFromYast()
|
|
string = yarg['string']
|
|
return string.strip()
|
|
|
|
def UI_GetFile(file):
|
|
debug_logger.debug('UI_GetFile: %s' % UI_mode)
|
|
filename = None
|
|
if UI_mode == 'text':
|
|
sys.stdout.write(file['description'] + '\n')
|
|
filename = sys.stdin.read()
|
|
else:
|
|
file['type'] = 'dialog-getfile'
|
|
SendDataToYast(file)
|
|
ypath, yarg = GetDataFromYast()
|
|
if yarg['answer'] == 'okay':
|
|
filename = yarg['filename']
|
|
return filename
|
|
|
|
def UI_BusyStart(message):
|
|
debug_logger.debug('UI_BusyStart: %s' % UI_mode)
|
|
if UI_mode == 'text':
|
|
UI_Info(message)
|
|
else:
|
|
SendDataToYast({
|
|
'type': 'dialog-busy-start',
|
|
'message': message
|
|
})
|
|
ypath, yarg = GetDataFromYast()
|
|
|
|
def UI_BusyStop():
|
|
debug_logger.debug('UI_BusyStop: %s' % UI_mode)
|
|
if UI_mode != 'text':
|
|
SendDataToYast({'type': 'dialog-busy-stop'})
|
|
ypath, yarg = GetDataFromYast()
|
|
|
|
CMDS = {
|
|
'CMD_ALLOW': _('(A)llow'),
|
|
'CMD_OTHER': _('(M)ore'),
|
|
'CMD_AUDIT_NEW': _('Audi(t)'),
|
|
'CMD_AUDIT_OFF': _('Audi(t) off'),
|
|
'CMD_AUDIT_FULL': _('Audit (A)ll'),
|
|
#'CMD_OTHER': '(O)pts',
|
|
'CMD_USER_ON': _('(O)wner permissions on'),
|
|
'CMD_USER_OFF': _('(O)wner permissions off'),
|
|
'CMD_DENY': _('(D)eny'),
|
|
'CMD_ABORT': _('Abo(r)t'),
|
|
'CMD_FINISHED': _('(F)inish'),
|
|
'CMD_ix': _('(I)nherit'),
|
|
'CMD_px': _('(P)rofile'),
|
|
'CMD_px_safe': _('(P)rofile Clean Exec'),
|
|
'CMD_cx': _('(C)hild'),
|
|
'CMD_cx_safe': _('(C)hild Clean Exec'),
|
|
'CMD_nx': _('(N)amed'),
|
|
'CMD_nx_safe': _('(N)amed Clean Exec'),
|
|
'CMD_ux': _('(U)nconfined'),
|
|
'CMD_ux_safe': _('(U)nconfined Clean Exec'),
|
|
'CMD_pix': _('(P)rofile Inherit'),
|
|
'CMD_pix_safe': _('(P)rofile Inherit Clean Exec'),
|
|
'CMD_cix': _('(C)hild Inherit'),
|
|
'CMD_cix_safe': _('(C)hild Inherit Clean Exec'),
|
|
'CMD_nix': _('(N)amed Inherit'),
|
|
'CMD_nix_safe': _('(N)amed Inherit Clean Exec'),
|
|
'CMD_EXEC_IX_ON': _('(X) ix On'),
|
|
'CMD_EXEC_IX_OFF': _('(X) ix Off'),
|
|
'CMD_SAVE': _('(S)ave Changes'),
|
|
'CMD_CONTINUE': _('(C)ontinue Profiling'),
|
|
'CMD_NEW': _('(N)ew'),
|
|
'CMD_GLOB': _('(G)lob'),
|
|
'CMD_GLOBEXT': _('Glob with (E)xtension'),
|
|
'CMD_ADDHAT': _('(A)dd Requested Hat'),
|
|
'CMD_USEDEFAULT': _('(U)se Default Hat'),
|
|
'CMD_SCAN': _('(S)can system log for AppArmor events'),
|
|
'CMD_HELP': _('(H)elp'),
|
|
'CMD_VIEW_PROFILE': _('(V)iew Profile'),
|
|
'CMD_USE_PROFILE': _('(U)se Profile'),
|
|
'CMD_CREATE_PROFILE': _('(C)reate New Profile'),
|
|
'CMD_UPDATE_PROFILE': _('(U)pdate Profile'),
|
|
'CMD_IGNORE_UPDATE': _('(I)gnore Update'),
|
|
'CMD_SAVE_CHANGES': _('(S)ave Changes'),
|
|
'CMD_SAVE_SELECTED': _('Save Selec(t)ed Profile'),
|
|
'CMD_UPLOAD_CHANGES': _('(U)pload Changes'),
|
|
'CMD_VIEW_CHANGES': _('(V)iew Changes'),
|
|
'CMD_VIEW_CHANGES_CLEAN': _('View Changes b/w (C)lean profiles'),
|
|
'CMD_VIEW': _('(V)iew'),
|
|
'CMD_ENABLE_REPO': _('(E)nable Repository'),
|
|
'CMD_DISABLE_REPO': _('(D)isable Repository'),
|
|
'CMD_ASK_NEVER': _('(N)ever Ask Again'),
|
|
'CMD_ASK_LATER': _('Ask Me (L)ater'),
|
|
'CMD_YES': _('(Y)es'),
|
|
'CMD_NO': _('(N)o'),
|
|
'CMD_ALL_NET': _('Allow All (N)etwork'),
|
|
'CMD_NET_FAMILY': _('Allow Network Fa(m)ily'),
|
|
'CMD_OVERWRITE': _('(O)verwrite Profile'),
|
|
'CMD_KEEP': _('(K)eep Profile'),
|
|
'CMD_CONTINUE': _('(C)ontinue'),
|
|
'CMD_IGNORE_ENTRY': _('(I)gnore')
|
|
}
|
|
|
|
def UI_PromptUser(q, params=''):
|
|
cmd = None
|
|
arg = None
|
|
if UI_mode == 'text':
|
|
cmd, arg = Text_PromptUser(q)
|
|
else:
|
|
q['type'] = 'wizard'
|
|
SendDataToYast(q)
|
|
ypath, yarg = GetDataFromYast()
|
|
if not cmd:
|
|
cmd = 'CMD_ABORT'
|
|
arg = yarg['selected']
|
|
if cmd == 'CMD_ABORT':
|
|
confirm_and_abort()
|
|
cmd = 'XXXINVALIDXXX'
|
|
elif cmd == 'CMD_FINISHED':
|
|
if not params:
|
|
confirm_and_finish()
|
|
cmd = 'XXXINVALIDXXX'
|
|
return (cmd, arg)
|
|
|
|
def confirm_and_abort():
|
|
ans = UI_YesNo(_('Are you sure you want to abandon this set of profile changes and exit?'), 'n')
|
|
if ans == 'y':
|
|
UI_Info(_('Abandoning all changes.'))
|
|
#shutdown_yast()
|
|
#for prof in created:
|
|
# delete_profile(prof)
|
|
sys.exit(0)
|
|
|
|
def UI_ShortMessage(title, message):
|
|
SendDataToYast({
|
|
'type': 'short-dialog-message',
|
|
'headline': title,
|
|
'message': message
|
|
})
|
|
ypath, yarg = GetDataFromYast()
|
|
|
|
def UI_LongMessage(title, message):
|
|
SendDataToYast({
|
|
'type': 'long-dialog-message',
|
|
'headline': title,
|
|
'message': message
|
|
})
|
|
ypath, yarg = GetDataFromYast()
|
|
|
|
def confirm_and_finish():
|
|
sys.stdout.write(_('FINISHING...\n'))
|
|
sys.exit(0)
|
|
|
|
def Text_PromptUser(question):
|
|
title = question['title']
|
|
explanation = question['explanation']
|
|
headers = question['headers']
|
|
functions = question['functions']
|
|
|
|
default = question['default']
|
|
options = question['options']
|
|
selected = question.get('selected', 0)
|
|
helptext = question['helptext']
|
|
if helptext:
|
|
functions.append('CMD_HELP')
|
|
|
|
menu_items = []
|
|
keys = dict()
|
|
|
|
for cmd in functions:
|
|
if not CMDS.get(cmd, False):
|
|
raise AppArmorException(_('PromptUser: Unknown command %s') % cmd)
|
|
|
|
menutext = CMDS[cmd]
|
|
|
|
key = get_translated_hotkey(menutext).lower()
|
|
# Duplicate hotkey
|
|
if keys.get(key, False):
|
|
raise AppArmorException(_('PromptUser: Duplicate hotkey for %s: %s ') % (cmd, menutext))
|
|
|
|
keys[key] = cmd
|
|
|
|
if default and default == cmd:
|
|
menutext = '[%s]' %menutext
|
|
|
|
menu_items.append(menutext)
|
|
|
|
default_key = 0
|
|
if default and CMDS[default]:
|
|
defaulttext = CMDS[default]
|
|
defmsg = _('PromptUser: Invalid hotkey in default item')
|
|
|
|
default_key = get_translated_hotkey(defaulttext, defmsg).lower()
|
|
|
|
if not keys.get(default_key, False):
|
|
raise AppArmorException(_('PromptUser: Invalid default %s') % default)
|
|
|
|
widest = 0
|
|
header_copy = headers[:]
|
|
while header_copy:
|
|
header = header_copy.pop(0)
|
|
header_copy.pop(0)
|
|
if len(header) > widest:
|
|
widest = len(header)
|
|
widest += 1
|
|
|
|
formatstr = '%-' + str(widest) + 's %s\n'
|
|
|
|
function_regexp = '^('
|
|
function_regexp += '|'.join(keys.keys())
|
|
if options:
|
|
function_regexp += '|\d'
|
|
function_regexp += ')$'
|
|
|
|
ans = 'XXXINVALIDXXX'
|
|
while not re.search(function_regexp, ans, flags=re.IGNORECASE):
|
|
|
|
prompt = '\n'
|
|
if title:
|
|
prompt += '= %s =\n\n' %title
|
|
|
|
if headers:
|
|
header_copy = headers[:]
|
|
while header_copy:
|
|
header = header_copy.pop(0)
|
|
value = header_copy.pop(0)
|
|
prompt += formatstr %(header+':', value)
|
|
prompt += '\n'
|
|
|
|
if explanation:
|
|
prompt += explanation + '\n\n'
|
|
|
|
if options:
|
|
for index, option in enumerate(options):
|
|
if selected == index:
|
|
format_option = ' [%s - %s]'
|
|
else:
|
|
format_option = ' %s - %s '
|
|
prompt += format_option %(index+1, option)
|
|
prompt += '\n'
|
|
|
|
prompt += ' / '.join(menu_items)
|
|
|
|
sys.stdout.write(prompt+'\n')
|
|
|
|
ans = getkey().lower()
|
|
|
|
if ans:
|
|
if ans == 'up':
|
|
if options and selected > 0:
|
|
selected -= 1
|
|
ans = 'XXXINVALIDXXX'
|
|
|
|
elif ans == 'down':
|
|
if options and selected < len(options)-1:
|
|
selected += 1
|
|
ans = 'XXXINVALIDXXX'
|
|
|
|
# elif keys.get(ans, False) == 'CMD_HELP':
|
|
# sys.stdout.write('\n%s\n' %helptext)
|
|
# ans = 'XXXINVALIDXXX'
|
|
|
|
elif is_number(ans) == 10:
|
|
# If they hit return choose default option
|
|
ans = default_key
|
|
|
|
elif options and re.search('^\d$', ans):
|
|
ans = int(ans)
|
|
if ans > 0 and ans <= len(options):
|
|
selected = ans - 1
|
|
ans = 'XXXINVALIDXXX'
|
|
|
|
if keys.get(ans, False) == 'CMD_HELP':
|
|
sys.stdout.write('\n%s\n' %helptext)
|
|
ans = 'again'
|
|
|
|
if keys.get(ans, False):
|
|
ans = keys[ans]
|
|
|
|
return ans, selected
|
|
|
|
def is_number(number):
|
|
try:
|
|
return int(number)
|
|
except:
|
|
return False
|