home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / share / pyshared / ufw / frontend.py < prev    next >
Encoding:
Python Source  |  2009-03-18  |  34.9 KB  |  990 lines

  1. #
  2. # frontend.py: frontend interface for ufw
  3. #
  4. # Copyright (C) 2008-2009 Canonical Ltd.
  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 version 3,
  8. #    as published by the Free Software Foundation.
  9. #
  10. #    This program is distributed in the hope that it will be useful,
  11. #    but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13. #    GNU General Public License for more details.
  14. #
  15. #    You should have received a copy of the GNU General Public License
  16. #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
  17. #
  18.  
  19. import re
  20. import os
  21. import sys
  22. import warnings
  23.  
  24. from ufw.common import UFWError
  25. import ufw.util
  26. from ufw.util import error, warn
  27. from ufw.backend_iptables import UFWBackendIptables
  28.  
  29. def allowed_command(cmd):
  30.     '''Return command if it is allowed, otherwise raise an exception'''
  31.     allowed_cmds = ['enable', 'disable', 'help', '--help', 'default', \
  32.                     'logging', 'status', 'version', '--version', 'allow', \
  33.                     'deny', 'reject', 'limit', 'reload', 'show' ]
  34.  
  35.     if not cmd.lower() in allowed_cmds:
  36.         raise ValueError()
  37.  
  38.     return cmd.lower()
  39.  
  40. def parse_command(argv):
  41.     '''Parse command. Returns tuple for action, rule, ip_version and dryrun.'''
  42.     action = ""
  43.     rule = ""
  44.     type = ""
  45.     from_type = "any"
  46.     to_type = "any"
  47.     from_service = ""
  48.     to_service = ""
  49.     dryrun = False
  50.     insert_pos = ""
  51.     logtype = ""
  52.     loglevel = ""
  53.  
  54.     if len(argv) > 1 and argv[1].lower() == "--dry-run":
  55.         dryrun = True
  56.         argv.remove(argv[1])
  57.  
  58.     remove = False
  59.     if len(argv) > 1 and argv[1].lower() == "delete":
  60.         remove = True
  61.         argv.remove(argv[1])
  62.  
  63.     nargs = len(argv)
  64.  
  65.     if nargs < 2:
  66.         raise ValueError()
  67.  
  68.     if argv[1].lower() in ['insert']:
  69.         action = argv[1].lower()
  70.     else:
  71.         action = allowed_command(argv[1])
  72.  
  73.     if action == "insert":
  74.         if nargs < 4:
  75.             raise ValueError()
  76.         insert_pos = argv[2]
  77.  
  78.         # Using position '0' adds rule at end, which is potentially confusing
  79.         # for the end user
  80.         if insert_pos == "0":
  81.             err_msg = _("Cannot insert rule at position '%s'") % (insert_pos)
  82.             raise UFWError(err_msg)
  83.  
  84.         # strip out 'insert NUM' and parse as normal
  85.         del argv[2]
  86.         del argv[1]
  87.         action = allowed_command(argv[1])
  88.         nargs = len(argv)
  89.  
  90.         # error if use insert with non-rule commands
  91.         if action != "allow" and action != "deny" and action != "reject" and \
  92.            action != "limit":
  93.             raise ValueError()
  94.  
  95.     if action == "logging":
  96.         if nargs < 3:
  97.             raise ValueError()
  98.         elif argv[2].lower() == "off":
  99.             action = "logging-off"
  100.         elif argv[2].lower() == "on" or argv[2].lower() == "low" or \
  101.              argv[2].lower() == "medium" or argv[2].lower() == "high" or \
  102.              argv[2].lower() == "full":
  103.             action = "logging-on"
  104.             if argv[2].lower() != "on":
  105.                 action += "_" + argv[2].lower()
  106.         else:
  107.             raise ValueError()
  108.  
  109.     if action == "status" and nargs > 2:
  110.         if argv[2].lower() == "verbose":
  111.             action = "status-verbose"
  112.         elif argv[2].lower() == "numbered":
  113.             action = "status-numbered"
  114.  
  115.     if action == "show":
  116.         if nargs == 2:
  117.             raise ValueError()
  118.         elif argv[2].lower() == "raw":
  119.             action = "show-raw"
  120.  
  121.     if action == "default":
  122.         if nargs < 3:
  123.             raise ValueError()
  124.         elif argv[2].lower() == "deny":
  125.             action = "default-deny"
  126.         elif argv[2].lower() == "allow":
  127.             action = "default-allow"
  128.         elif argv[2].lower() == "reject":
  129.             action = "default-reject"
  130.         else:
  131.             raise ValueError()
  132.  
  133.     if action == "allow" or action == "deny" or action == "reject" or \
  134.        action == "limit":
  135.         if nargs > 2 and (argv[2].lower() == "log" or \
  136.                           argv[2].lower() == 'log-all'):
  137.             if nargs < 4:
  138.                 raise ValueError()
  139.             logtype = argv[2].lower()
  140.  
  141.             # strip out 'log' or 'log-all' and parse as normal
  142.             del argv[2]
  143.             nargs = len(argv)
  144.  
  145.         if nargs < 3 or nargs > 12:
  146.             raise ValueError()
  147.  
  148.         rule_action = action
  149.         if logtype != "":
  150.             rule_action += "_" + logtype
  151.         rule = ufw.common.UFWRule(rule_action, "any", "any")
  152.         if remove:
  153.             rule.remove = remove
  154.         elif insert_pos != "":
  155.             try:
  156.                 rule.set_position(insert_pos)
  157.             except Exception:
  158.                 raise
  159.         if nargs == 3:
  160.             # Short form where only app or port/proto is given
  161.             if ufw.applications.valid_profile_name(argv[2]):
  162.                 # Check if name collision with /etc/services. If so, use
  163.                 # /etc/services instead of application profile
  164.                 try:
  165.                     ufw.util.get_services_proto(argv[2])
  166.                 except Exception:
  167.                     type = "both"
  168.                     rule.dapp = argv[2]
  169.                     rule.set_port(argv[2], "dst")
  170.             if rule.dapp == "":
  171.                 try:
  172.                     (port, proto) = ufw.util.parse_port_proto(argv[2])
  173.                 except UFWError:
  174.                     err_msg = _("Bad port")
  175.                     raise UFWError(err_msg)
  176.  
  177.                 if not re.match('^\d([0-9,:]*\d+)*$', port):
  178.                     if ',' in port or ':' in port:
  179.                         err_msg = _("Port ranges must be numeric")
  180.                         raise UFWError(err_msg)
  181.                     to_service = port
  182.  
  183.                 try:
  184.                     rule.set_protocol(proto)
  185.                     rule.set_port(port, "dst")
  186.                     type = "both"
  187.                 except UFWError:
  188.                     err_msg = _("Bad port")
  189.                     raise UFWError(err_msg)
  190.         elif nargs % 2 != 0:
  191.             err_msg = _("Wrong number of arguments")
  192.             raise UFWError(err_msg)
  193.         elif not 'from' in argv and not 'to' in argv:
  194.             err_msg = _("Need 'to' or 'from' clause")
  195.             raise UFWError(err_msg)
  196.         else:
  197.             # Full form with PF-style syntax
  198.             keys = [ 'proto', 'from', 'to', 'port', 'app' ]
  199.  
  200.             # quick check
  201.             if argv.count("to") > 1 or \
  202.                argv.count("from") > 1 or \
  203.                argv.count("proto") > 1 or \
  204.                argv.count("port") > 2 or \
  205.                argv.count("app") > 2 or \
  206.                argv.count("app") > 0 and argv.count("proto") > 0:
  207.                 err_msg = _("Improper rule syntax")
  208.                 raise UFWError(err_msg)
  209.  
  210.             i = 1
  211.             loc = ""
  212.             for arg in argv[1:]:
  213.                 if i % 2 == 0 and argv[i] not in keys:
  214.                     err_msg = _("Invalid token '%s'") % (argv[i])
  215.                     raise UFWError(err_msg)
  216.                 if arg == "proto":
  217.                     if i+1 < nargs:
  218.                         try:
  219.                             rule.set_protocol(argv[i+1])
  220.                         except Exception:
  221.                             raise
  222.                     else:
  223.                         err_msg = _("Invalid 'proto' clause")
  224.                         raise UFWError(err_msg)
  225.                 elif arg == "from":
  226.                     if i+1 < nargs:
  227.                         try:
  228.                             faddr = argv[i+1].lower()
  229.                             if faddr == "any":
  230.                                 faddr = "0.0.0.0/0"
  231.                                 from_type = "any"
  232.                             else:
  233.                                 if ufw.util.valid_address(faddr, "6"):
  234.                                     from_type = "v6"
  235.                                 else:
  236.                                     from_type = "v4"
  237.                             rule.set_src(faddr)
  238.                         except Exception:
  239.                             raise
  240.                         loc = "src"
  241.                     else:
  242.                         err_msg = _("Invalid 'from' clause")
  243.                         raise UFWError(err_msg)
  244.                 elif arg == "to":
  245.                     if i+1 < nargs:
  246.                         try:
  247.                             saddr = argv[i+1].lower()
  248.                             if saddr == "any":
  249.                                 saddr = "0.0.0.0/0"
  250.                                 to_type = "any"
  251.                             else:
  252.                                 if ufw.util.valid_address(saddr, "6"):
  253.                                     to_type = "v6"
  254.                                 else:
  255.                                     to_type = "v4"
  256.                             rule.set_dst(saddr)
  257.                         except Exception:
  258.                             raise
  259.                         loc = "dst"
  260.                     else:
  261.                         err_msg = _("Invalid 'to' clause")
  262.                         raise UFWError(err_msg)
  263.                 elif arg == "port" or arg == "app":
  264.                     if i+1 < nargs:
  265.                         if loc == "":
  266.                             err_msg = _("Need 'from' or 'to' with '%s'") % \
  267.                                         (arg)
  268.                             raise UFWError(err_msg)
  269.  
  270.                         tmp = argv[i+1]
  271.                         if arg == "app":
  272.                             if loc == "src":
  273.                                 rule.sapp = tmp
  274.                             else:
  275.                                 rule.dapp = tmp
  276.                         elif not re.match('^\d([0-9,:]*\d+)*$', tmp):
  277.                             if ',' in tmp or ':' in tmp:
  278.                                 err_msg = _("Port ranges must be numeric")
  279.                                 raise UFWError(err_msg)
  280.  
  281.                             if loc == "src":
  282.                                 from_service = tmp
  283.                             else:
  284.                                 to_service = tmp
  285.                         try:
  286.                             rule.set_port(tmp, loc)
  287.                         except Exception:
  288.                             raise
  289.                     else:
  290.                         err_msg = _("Invalid 'port' clause")
  291.                         raise UFWError(err_msg)
  292.                 i += 1
  293.  
  294.             # Figure out the type of rule (IPv4, IPv6, or both) this is
  295.             if from_type == "any" and to_type == "any":
  296.                 type = "both"
  297.             elif from_type != "any" and to_type != "any" and \
  298.                  from_type != to_type:
  299.                 err_msg = _("Mixed IP versions for 'from' and 'to'")
  300.                 raise UFWError(err_msg)
  301.             elif from_type != "any":
  302.                 type = from_type
  303.             elif to_type != "any":
  304.                 type = to_type
  305.  
  306.     # Adjust protocol
  307.     if to_service != "" or from_service != "":
  308.         proto = ""
  309.         if to_service != "":
  310.             try:
  311.                 proto = ufw.util.get_services_proto(to_service)
  312.             except Exception:
  313.                 err_msg = _("Could not find protocol")
  314.                 raise UFWError(err_msg)
  315.         if from_service != "":
  316.             if proto == "any" or proto == "":
  317.                 try:
  318.                     proto = ufw.util.get_services_proto(from_service)
  319.                 except Exception:
  320.                     err_msg = _("Could not find protocol")
  321.                     raise UFWError(err_msg)
  322.             else:
  323.                 try:
  324.                     tmp = ufw.util.get_services_proto(from_service)
  325.                 except Exception:
  326.                     err_msg = _("Could not find protocol")
  327.                     raise UFWError(err_msg)
  328.                 if proto == "any" or proto == tmp:
  329.                     proto = tmp
  330.                 elif tmp == "any":
  331.                     pass
  332.                 else:
  333.                     err_msg = _("Protocol mismatch (from/to)")
  334.                     raise UFWError(err_msg)
  335.  
  336.         # Verify found proto with specified proto
  337.         if rule.protocol == "any":
  338.             rule.set_protocol(proto)
  339.         elif proto != "any" and rule.protocol != proto:
  340.             err_msg = _("Protocol mismatch with specified protocol %s") % \
  341.                         (rule.protocol)
  342.             raise UFWError(err_msg)
  343.  
  344.     # Verify protocol not specified with application rule
  345.     if rule and rule.protocol != "any" and \
  346.        (rule.sapp != "" or rule.dapp != ""):
  347.         app = ""
  348.         if rule.dapp:
  349.             app = rule.dapp
  350.         else:
  351.             app = rule.sapp
  352.         err_msg = _("Improper rule syntax ('%s' specified with app rule)") % \
  353.                    (rule.protocol)
  354.         raise UFWError(err_msg)
  355.  
  356.     return (action, rule, type, dryrun)
  357.  
  358.  
  359. def parse_application_command(argv):
  360.     '''Parse applications command. Returns tuple for action and profile name'''
  361.     name = ""
  362.     action = ""
  363.     dryrun = False
  364.     addnew = False
  365.  
  366.     if len(argv) < 3 or argv[1].lower() != "app":
  367.         raise ValueError()
  368.  
  369.     argv.remove("app")
  370.     nargs = len(argv)
  371.  
  372.     if len(argv) > 1 and argv[1].lower() == "--dry-run":
  373.         dryrun = True
  374.         argv.remove(argv[1])
  375.  
  376.     app_cmds = ['list', 'info', 'default', 'update']
  377.  
  378.     if not argv[1].lower() in app_cmds:
  379.         raise ValueError()
  380.     else:
  381.         action = argv[1].lower()
  382.  
  383.     if action == "info" or action == "update":
  384.         if nargs >= 4 and argv[2] == "--add-new":
  385.             addnew = True
  386.             argv.remove("--add-new")
  387.             nargs = len(argv)
  388.  
  389.         if nargs < 3:
  390.             raise ValueError()
  391.  
  392.         # Handle quoted name with spaces in it by stripping Python's ['...']
  393.         # list as string text.
  394.         name = str(argv[2]).strip("[']")
  395.  
  396.         if addnew:
  397.             action += "-with-new"
  398.  
  399.     if action == "list" and nargs != 2:
  400.         raise ValueError()
  401.  
  402.     if action == "default":
  403.         if nargs < 3:
  404.             raise ValueError()
  405.         if argv[2].lower() == "allow":
  406.             action = "default-allow"
  407.         elif argv[2].lower() == "deny":
  408.             action = "default-deny"
  409.         elif argv[2].lower() == "reject":
  410.             action = "default-reject"
  411.         elif argv[2].lower() == "skip":
  412.             action = "default-skip"
  413.         else:
  414.             raise ValueError()
  415.  
  416.     return (action, name, dryrun)
  417.  
  418.  
  419. def get_command_help():
  420.     '''Print help message'''
  421.     msg = _('''
  422. Usage: ''') + ufw.common.programName + _(''' COMMAND
  423.  
  424. Commands:
  425.  enable                enables the firewall
  426.  disable            disables the firewall
  427.  default ARG            set default policy to ALLOW, DENY or REJECT
  428.  logging ARG            set logging to OFF, ON or LEVEL
  429.  allow|deny|reject ARG        add allow, deny or reject RULE
  430.  delete RULE             delete the RULE
  431.  insert NUM RULE         insert RULE at NUM
  432.  status             show firewall status
  433.  status numbered        show firewall status as numbered list of RULES
  434.  show ARG            show firewall report
  435.  version            display version information
  436.  
  437. Application profile commands:
  438.  app list            list application profiles
  439.  app info PROFILE        show information on PROFILE
  440.  app update PROFILE        update PROFILE
  441.  app default ARG        set profile policy to ALLOW, DENY, REJECT or
  442.                 SKIP
  443. ''')
  444.     return (msg)
  445.  
  446.  
  447. class UFWFrontend:
  448.     '''UI'''
  449.     def __init__(self, dryrun, backend_type="iptables"):
  450.         if backend_type == "iptables":
  451.             try:
  452.                 self.backend = UFWBackendIptables(dryrun)
  453.             except Exception:
  454.                 raise
  455.         else:
  456.             raise UFWError("Unsupported backend type '%s'" % (backend_type))
  457.  
  458.         self._init_input_strings()
  459.  
  460.     def _init_input_strings(self):
  461.         '''Initialize input strings for translations'''
  462.         self.no = _("n")
  463.         self.yes = _("y")
  464.         self.yes_full = _("yes")
  465.  
  466.     def set_enabled(self, enabled):
  467.         '''Toggles ENABLED state in of <config_dir>/ufw/ufw.conf'''
  468.         res = ""
  469.  
  470.         str = "no"
  471.         if enabled:
  472.             str = "yes"
  473.  
  474.         changed = False
  475.         if (enabled and not self.backend._is_enabled()) or \
  476.            (not enabled and self.backend._is_enabled()):
  477.             changed = True
  478.  
  479.         # Update the config files when toggling enable/disable
  480.         if changed:
  481.             try:
  482.                 self.backend.set_default(self.backend.files['conf'], \
  483.                                          "ENABLED", str)
  484.             except UFWError, e:
  485.                 error(e.value)
  486.  
  487.         error_str = ""
  488.         if enabled:
  489.             try:
  490.                 self.backend.start_firewall()
  491.             except UFWError, e:
  492.                 if changed:
  493.                     error_str = e.value
  494.  
  495.             if error_str != "":
  496.                 # Revert config files when toggling enable/disable and
  497.                 # firewall failed to start
  498.                 try:
  499.                     self.backend.set_default(self.backend.files['conf'], \
  500.                                              "ENABLED", "no")
  501.                 except UFWError, e:
  502.                     error(e.value)
  503.  
  504.                 # Report the error
  505.                 error(error_str)
  506.  
  507.             res = _("Firewall is active and enabled on system startup")
  508.         else:
  509.             try:
  510.                 self.backend.stop_firewall()
  511.             except UFWError, e:
  512.                 error(e.value)
  513.  
  514.             res = _("Firewall stopped and disabled on system startup")
  515.  
  516.         return res
  517.  
  518.     def set_default_policy(self, policy):
  519.         '''Sets default policy of firewall'''
  520.         res = ""
  521.         try:
  522.             res = self.backend.set_default_policy(policy)
  523.             if self.backend._is_enabled():
  524.                 self.backend.stop_firewall()
  525.                 self.backend.start_firewall()
  526.         except UFWError, e:
  527.             error(e.value)
  528.  
  529.         return res
  530.  
  531.     def set_loglevel(self, level):
  532.         '''Sets log level of firewall'''
  533.         res = ""
  534.         try:
  535.             res = self.backend.set_loglevel(level)
  536.         except UFWError, e:
  537.             error(e.value)
  538.  
  539.         return res
  540.  
  541.     def get_status(self, verbose=False, show_count=False):
  542.         '''Shows status of firewall'''
  543.         try:
  544.             out = self.backend.get_status(verbose, show_count)
  545.         except UFWError, e:
  546.             error(e.value)
  547.  
  548.         return out
  549.  
  550.     def get_show_raw(self):
  551.         '''Shows raw output of firewall'''
  552.         try:
  553.             out = self.backend.get_running_raw()
  554.         except UFWError, e:
  555.             error(e.value)
  556.  
  557.         return out
  558.  
  559.     def set_rule(self, rule, ip_version):
  560.         '''Updates firewall with rule'''
  561.         res = ""
  562.         err_msg = ""
  563.         tmp = ""
  564.         rules = []
  565.  
  566.         if rule.dapp == "" and rule.sapp == "":
  567.             rules.append(rule)
  568.         else:
  569.             tmprules = []
  570.             try:
  571.                 if rule.remove:
  572.                     if ip_version == "v4":
  573.                         tmprules = self.backend.get_app_rules_from_system(rule, False)
  574.                     elif ip_version == "v6":
  575.                         tmprules = self.backend.get_app_rules_from_system(rule, True)
  576.                     elif ip_version == "both":
  577.                         tmprules = self.backend.get_app_rules_from_system(rule, False)
  578.                         tmprules6 = self.backend.get_app_rules_from_system(rule, True)
  579.                         # Only add rules that are different by more than v6 (we
  580.                         # will handle 'ip_version == both' specially, below).
  581.                         for x in tmprules:
  582.                             for y in tmprules6:
  583.                                 prev6 = y.v6
  584.                                 y.v6 = False
  585.                                 if not x.match(y):
  586.                                     y.v6 = prev6
  587.                                     tmprules.append(y)
  588.                     else:
  589.                         err_msg = _("Invalid IP version '%s'") % (ip_version)
  590.                         raise UFWError(err_msg)
  591.  
  592.                     # Don't process removal of non-existing application rules
  593.                     if len(tmprules) == 0 and not self.backend.dryrun:
  594.                         tmp =  _("Could not delete non-existent rule")
  595.                         if ip_version == "v4":
  596.                             res = tmp
  597.                         elif ip_version == "v6":
  598.                             res = tmp + " (v6)"
  599.                         elif ip_version == "both":
  600.                             res = tmp + "\n" + tmp + " (v6)"
  601.                         return res
  602.  
  603.                     for tmp in tmprules:
  604.                         r = tmp.dup_rule()
  605.                         r.remove = rule.remove
  606.                         r.set_action(rule.action)
  607.                         r.set_logtype(rule.logtype)
  608.                         rules.append(r)
  609.                 else:
  610.                     rules = self.backend.get_app_rules_from_template(rule)
  611.                     # Reverse the order of rules for inserted rules, so they
  612.                     # are inserted in the right order
  613.                     if rule.position > 0:
  614.                         rules.reverse()
  615.             except Exception:
  616.                 raise
  617.  
  618.         count = 0
  619.         set_error = False
  620.         pos_err_msg = _("Invalid position '")
  621.         num_v4 = self.backend.get_rules_count(False)
  622.         num_v6 = self.backend.get_rules_count(True)
  623.         for i, r in enumerate(rules):
  624.             count = i
  625.             if r.position > num_v4 + num_v6:
  626.                 pos_err_msg += str(r.position) + "'"
  627.                 raise UFWError(pos_err_msg)
  628.             try:
  629.                 if self.backend.use_ipv6():
  630.                     if ip_version == "v4":
  631.                         if r.position > num_v4:
  632.                             pos_err_msg += str(r.position) + "'"
  633.                             raise UFWError(pos_err_msg)
  634.                         r.set_v6(False)
  635.                         tmp = self.backend.set_rule(r)
  636.                     elif ip_version == "v6":
  637.                         if r.position > num_v4:
  638.                             r.set_position(r.position - num_v4)
  639.                         elif r.position != 0 and r.position <= num_v4:
  640.                             pos_err_msg += str(r.position) + "'"
  641.                             raise UFWError(pos_err_msg)
  642.                         r.set_v6(True)
  643.                         tmp = self.backend.set_rule(r)
  644.                     elif ip_version == "both":
  645.                         user_pos = r.position # user specified position
  646.                         r.set_v6(False)
  647.                         if not r.remove and user_pos > num_v4:
  648.                 # The user specified a v6 rule, so try to find a
  649.                 # match in the v4 rules and use its position.
  650.                             p = self.backend.find_other_position(user_pos - \
  651.                                                                  num_v4, True)
  652.                             if p > 0:
  653.                                 r.set_position(p)
  654.                             else:
  655.                                 # If not found, then add the rule
  656.                                 r.set_position(0)
  657.                         tmp = self.backend.set_rule(r)
  658.  
  659.                         # We need to readjust the position since the number
  660.                         # the number of ipv4 rules increased
  661.                         if not r.remove and user_pos > 0:
  662.                             num_v4 = self.backend.get_rules_count(False)
  663.                             r.set_position(user_pos + 1)
  664.  
  665.                         r.set_v6(True)
  666.                         if not r.remove and r.position > 0 and \
  667.                            r.position <= num_v4:
  668.                 # The user specified a v4 rule, so try to find a
  669.                 # match in the v6 rules and use its position.
  670.                             p = self.backend.find_other_position(r.position, \
  671.                                                                  False)
  672.                             if p > 0:
  673.                                 # Subtract count since the list is reversed
  674.                                 r.set_position(p - count)
  675.                             else:
  676.                                 # If not found, then add the rule
  677.                                 r.set_position(0)
  678.                         if tmp != "":
  679.                             tmp += "\n"
  680.  
  681.                         # Readjust position to send to set_rule
  682.                         if not r.remove and r.position > num_v4:
  683.                             r.set_position(r.position - num_v4)
  684.  
  685.                         tmp += self.backend.set_rule(r)
  686.                     else:
  687.                         err_msg = _("Invalid IP version '%s'") % (ip_version)
  688.                         raise UFWError(err_msg)
  689.                 else:
  690.                     if ip_version == "v4" or ip_version == "both":
  691.                         r.set_v6(False)
  692.                         tmp = self.backend.set_rule(r)
  693.                     elif ip_version == "v6":
  694.                         err_msg = _("IPv6 support not enabled")
  695.                         raise UFWError(err_msg)
  696.                     else:
  697.                         err_msg = _("Invalid IP version '%s'") % (ip_version)
  698.                         raise UFWError(err_msg)
  699.             except UFWError, e:
  700.                 err_msg = e.value
  701.                 set_error = True
  702.                 break
  703.  
  704.             if r.updated:
  705.                 warn_msg = _("Rule changed after normalization")
  706.                 warnings.warn(warn_msg)
  707.  
  708.         if not set_error:
  709.             # Just return the last result if no error
  710.             res += tmp
  711.         elif len(rules) == 1:
  712.             # If no error, and just one rule, error out
  713.             error(err_msg)
  714.         else:
  715.         # If error and more than one rule, delete the successfully added
  716.         # rules in reverse order
  717.             undo_error = False
  718.             indexes = range(count+1)
  719.             indexes.reverse()
  720.             for j in indexes:
  721.                 if count > 0 and rules[j]:
  722.                     backout_rule = rules[j].dup_rule()
  723.                     backout_rule.remove = True
  724.                     try:
  725.                         self.set_rule(backout_rule, ip_version)
  726.                     except Exception:
  727.                         # Don't fail, so we can try to backout more
  728.                         undo_error = True
  729.                         warn_msg = _("Could not back out rule '%s'") % \
  730.                                      r.format_rule()
  731.                         warn(warn_msg)
  732.  
  733.             err_msg += _("\nError applying application rules.")
  734.             if undo_error:
  735.                 err_msg += _(" Some rules could not be unapplied.")
  736.             else:
  737.                 err_msg += _(" Attempted rules successfully unapplied.")
  738.  
  739.             raise UFWError(err_msg)
  740.  
  741.         return res
  742.  
  743.     def do_action(self, action, rule, ip_version):
  744.         '''Perform action on rule. action, rule and ip_version are usually
  745.            based on return values from parse_command().
  746.         '''
  747.         res = ""
  748.         if action.startswith("logging-on"):
  749.             tmp = action.split('_')
  750.             if len(tmp) > 1:
  751.                 res = self.set_loglevel(tmp[1])
  752.             else:
  753.                 res = self.set_loglevel("on")
  754.         elif action == "logging-off":
  755.             res = self.set_loglevel("off")
  756.         elif action == "default-allow":
  757.             res = self.set_default_policy("allow")
  758.         elif action == "default-deny":
  759.             res = self.set_default_policy("deny")
  760.         elif action == "default-reject":
  761.             res = self.set_default_policy("reject")
  762.         elif action == "status":
  763.             res = self.get_status()
  764.         elif action == "status-verbose":
  765.             res = self.get_status(True)
  766.         elif action == "show-raw":
  767.             res = self.get_show_raw()
  768.         elif action == "status-numbered":
  769.             res = self.get_status(False, True)
  770.         elif action == "enable":
  771.             res = self.set_enabled(True)
  772.         elif action == "disable":
  773.             res = self.set_enabled(False)
  774.         elif action == "reload":
  775.             if self.backend._is_enabled():
  776.                 self.set_enabled(False)
  777.                 self.set_enabled(True)
  778.                 res = _("Firewall reloaded")
  779.             else:
  780.                 res = _("Firewall not enabled (skipping reload)")
  781.         elif action == "allow" or action == "deny" or action == "reject" or \
  782.              action == "limit":
  783.             # allow case insensitive matches for application rules
  784.             try:
  785.                 if rule.dapp != "":
  786.                     tmp = self.backend.find_application_name(rule.dapp)
  787.                     if tmp != rule.dapp:
  788.                         rule.dapp = tmp
  789.                         rule.set_port(tmp, "dst")
  790.                 if rule.sapp != "":
  791.                     tmp = self.backend.find_application_name(rule.sapp)
  792.                     if tmp != rule.sapp:
  793.                         rule.sapp = tmp
  794.                         rule.set_port(tmp, "src")
  795.             except UFWError, e:
  796.                 error(e.value)
  797.             res = self.set_rule(rule, ip_version)
  798.         else:
  799.             err_msg = _("Unsupported action '%s'") % (action)
  800.             raise UFWError(err_msg)
  801.  
  802.         return res
  803.  
  804.     def set_default_application_policy(self, policy):
  805.         '''Sets default application policy of firewall'''
  806.         res = ""
  807.         try:
  808.             res = self.backend.set_default_application_policy(policy)
  809.         except UFWError, e:
  810.             error(e.value)
  811.  
  812.         return res
  813.  
  814.     def get_application_list(self):
  815.         '''Display list of known application profiles'''
  816.         names = self.backend.profiles.keys()
  817.         names.sort()
  818.         rstr = _("Available applications:")
  819.         for n in names:
  820.             rstr += "\n  %s" % (n)
  821.         return rstr
  822.  
  823.     def get_application_info(self, pname):
  824.         '''Display information on profile'''
  825.         names = []
  826.         if pname == "all":
  827.             names = self.backend.profiles.keys()
  828.             names.sort()
  829.         else:
  830.             if not ufw.applications.valid_profile_name(pname):
  831.                 err_msg = _("Invalid profile name")
  832.                 raise UFWError(err_msg)
  833.             names.append(pname)
  834.  
  835.         rstr = ""
  836.         for name in names:
  837.             if not self.backend.profiles.has_key(name) or \
  838.                not self.backend.profiles[name]:
  839.                 err_msg = _("Could not find profile '%s'") % (name)
  840.                 raise UFWError(err_msg)
  841.  
  842.             if not ufw.applications.verify_profile(name, \
  843.                self.backend.profiles[name]):
  844.                 err_msg = _("Invalid profile")
  845.                 raise UFWError(err_msg)
  846.  
  847.             rstr += _("Profile: %s\n") % (name)
  848.             rstr += _("Title: %s\n") % (ufw.applications.get_title(\
  849.                                         self.backend.profiles[name]))
  850.  
  851.             rstr += _("Description: %s\n\n") % \
  852.                                             (ufw.applications.get_description(\
  853.                                              self.backend.profiles[name]))
  854.  
  855.             ports = ufw.applications.get_ports(self.backend.profiles[name])
  856.             if len(ports) > 1 or ',' in ports[0]:
  857.                 rstr += _("Ports:")
  858.             else:
  859.                 rstr += _("Port:")
  860.  
  861.             for p in ports:
  862.                 rstr += "\n  %s" % (p)
  863.  
  864.             if name != names[len(names)-1]:
  865.                 rstr += "\n\n--\n\n"
  866.  
  867.         return ufw.util.wrap_text(rstr)
  868.  
  869.     def application_update(self, profile):
  870.         '''Refresh application profile'''
  871.         rstr = ""
  872.         allow_reload = True
  873.         trigger_reload = False
  874.  
  875.         if self.backend.do_checks and ufw.util.under_ssh():
  876.             # Don't reload the firewall if running under ssh
  877.             allow_reload = False
  878.  
  879.         if profile == "all":
  880.             profiles = self.backend.profiles.keys()
  881.             profiles.sort()
  882.             for p in profiles:
  883.                 (tmp, found) = self.backend.update_app_rule(p)
  884.                 if found:
  885.                     if tmp != "":
  886.                         tmp += "\n"
  887.                     rstr += tmp
  888.                     trigger_reload = found
  889.         else:
  890.             (rstr, trigger_reload) = self.backend.update_app_rule(profile)
  891.             if rstr != "":
  892.                 rstr += "\n"
  893.  
  894.         if trigger_reload and self.backend._is_enabled():
  895.             if allow_reload:
  896.                 try:
  897.                     self.backend._reload_user_rules()
  898.                 except Exception:
  899.                     raise
  900.                 rstr += _("Firewall reloaded")
  901.             else:
  902.                 rstr += _("Skipped reloading firewall")
  903.  
  904.         return rstr
  905.  
  906.     def application_add(self, profile):
  907.         '''Refresh application profile'''
  908.         rstr = ""
  909.         policy = ""
  910.  
  911.         if profile == "all":
  912.             err_msg = _("Cannot specify 'all' with '--add-new'")
  913.             raise UFWError(err_msg)
  914.  
  915.         default = self.backend.defaults['default_application_policy']
  916.         if default == "skip":
  917.             ufw.util.debug("Policy is '%s', not adding profile '%s'" % \
  918.                            (policy, profile))
  919.             return rstr
  920.         elif default == "accept":
  921.             policy = "allow"
  922.         elif default == "drop":
  923.             policy = "deny"
  924.         elif default == "reject":
  925.             policy = "reject"
  926.         else:
  927.             err_msg = _("Unknown policy '%s'") % (default)
  928.             raise UFWError(err_msg)
  929.  
  930.         args = [ 'ufw' ]
  931.         if self.backend.dryrun:
  932.             args.append("--dry-run")
  933.  
  934.         args += [ policy, profile ]
  935.         try:
  936.             (action, rule, ip_version, self.backend.dryrun) = \
  937.                 parse_command(args)
  938.         except Exception:
  939.             raise
  940.  
  941.         rstr = self.do_action(action, rule, ip_version)
  942.         return rstr
  943.  
  944.     def do_application_action(self, action, profile):
  945.         '''Perform action on profile. action and profile are usually based on
  946.            return values from parse_applications_command().
  947.         '''
  948.         res = ""
  949.         if action == "default-allow":
  950.             res = self.set_default_application_policy("allow")
  951.         elif action == "default-deny":
  952.             res = self.set_default_application_policy("deny")
  953.         elif action == "default-reject":
  954.             res = self.set_default_application_policy("reject")
  955.         elif action == "default-skip":
  956.             res = self.set_default_application_policy("skip")
  957.         elif action == "list":
  958.             res = self.get_application_list()
  959.         elif action == "info":
  960.             res = self.get_application_info(profile)
  961.         elif action == "update" or action == "update-with-new":
  962.             str1 = self.application_update(profile)
  963.             str2 = ""
  964.             if action == "update-with-new":
  965.                 str2 = self.application_add(profile)
  966.  
  967.             if str1 != "" and str2 != "":
  968.                 str1 += "\n"
  969.             res = str1 + str2
  970.         else:
  971.             err_msg = _("Unsupported action '%s'") % (action)
  972.             raise UFWError(err_msg)
  973.  
  974.         return res
  975.  
  976.     def continue_under_ssh(self):
  977.         '''If running under ssh, prompt the user for confirmation'''
  978.         proceed = True
  979.         if self.backend.do_checks and ufw.util.under_ssh():
  980.             prompt = _("Command may disrupt existing ssh connections.")
  981.             prompt += _(" Proceed with operation (%s|%s)? ") % \
  982.                        (self.yes, self.no)
  983.             os.write(sys.stdout.fileno(), prompt)
  984.             ans = sys.stdin.readline().lower().strip()
  985.             if ans != "y" and ans != self.yes and ans != self.yes_full:
  986.                 proceed = False
  987.  
  988.         return proceed
  989.  
  990.