Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added wol with Mikrotik router #31

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions gwakeonlan/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,24 @@ def show_message_dialog_yesno(parent, message, title, default_response):
return response


def split_credentials(destination):
"""Split the destination: username@destination#interface"""
dst = destination.split(sep='@', maxsplit=1)
username = None
router = None
interface = None
if len(dst) == 2:
username = dst[0]
dst = dst[1]
else:
dst = destination
dst = dst.split(sep='#', maxsplit=1)
if len(dst) == 2:
interface = dst[1]
router = dst[0]
return username, router, interface


def wake_on_lan(mac_address, port_number, destination):
"""Turn on remote machine using Wake On LAN"""
logging.info(f'turning on: {mac_address} '
Expand Down
58 changes: 58 additions & 0 deletions gwakeonlan/mikrotik.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
##
# Project: gWakeOnLAN
# Description: Wake up your machines using Wake on LAN
# Proj Author: Fabio Castelli (Muflone) <[email protected]>
# File Author: Mohammad Bahoosh (Moisrex) <[email protected]>
# Copyright: 2009-2022 Fabio Castelli
# License: GPL-3+
# 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 <https://www.gnu.org/licenses/>.
##
import paramiko
import logging
import socket


class Mikrotik:

def wol(self, mac_address, username, router, interface, password):
"""Ask a Mikrotik router to turn on the machine on your behalf"""
logging.info(f'Asking: {router} '
f'to turning on: {mac_address} '
f'in interface {interface}')
try:
ssh = paramiko.SSHClient()
ssh.load_system_host_keys()
ssh.connect(router, username=username, password=password)

# Command can be found here:
# https://wiki.mikrotik.com/wiki/Manual:Tools/Wake_on_lan
stdin = None
stdout = None
stderr = None
if interface == "":
stdin, stdout, stderr = ssh.exec_command(f"/tool/wol mac=\"{mac_address}\"")
else:
stdin, stdout, stderr = ssh.exec_command(f"/tool/wol mac=\"{mac_address}\" interface=\"{interface}\"")
if stdout:
logging.info(f'{router} returned: {stdout.read()}')
ssh.close()
return True
except paramiko.SSHException as err:
msg = f'Failed to connect to router {router}, error: {err}'
logging.error(msg)
return msg
except socket.gaierror as err:
msg = f"Can't connect to {router}; {err}"
logging.error(msg)
return msg
63 changes: 57 additions & 6 deletions gwakeonlan/ui/detail.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from gwakeonlan.functions import format_mac_address
from gwakeonlan.localize import _
from gwakeonlan.ui.base import UIBase
from gwakeonlan.functions import split_credentials

SECTION_WINDOW_NAME = 'detail'

Expand Down Expand Up @@ -111,7 +112,15 @@ def destroy(self):

def do_get_destination(self):
"""Return the destination host"""
return self.ui.text_destination_host.get_text()
request_type_mikrotik = self.ui.radio_request_mikrotik.get_active()
destination = self.ui.text_destination_host.get_text()
if request_type_mikrotik:
username = self.ui.text_username.get_text() + '@'
interface = self.ui.text_interface.get_text()
if interface != "":
interface = "#" + interface
return f'{username}{destination}{interface}'
return destination

def do_get_mac_address(self):
"""Return the MAC address"""
Expand All @@ -134,28 +143,70 @@ def do_load_data(self, machine_name, mac_address, portnr, destination):
self.ui.text_machine_name.set_text(machine_name)
self.ui.text_mac_address.set_text(mac_address)
self.ui.spin_port_number.set_value(portnr)
self.ui.text_destination_host.set_text(destination)
if destination in (BROADCAST_ADDRESS, ''):
username, router, interface = split_credentials(destination)
if username:
self.ui.text_username.set_text(username)
if interface:
self.ui.text_interface.set_text(interface)
self.ui.text_destination_host.set_text(router)
if username or interface:
self.ui.radio_request_mikrotik.set_active(True)
self.ui.text_destination_host.set_sensitive(True)
self.ui.text_username.set_sensitive(True)
self.ui.text_interface.set_sensitive(True)
elif router in (BROADCAST_ADDRESS, ''):
self.ui.radio_request_local.set_active(True)
self.ui.text_destination_host.set_sensitive(False)
else:
self.ui.radio_request_internet.set_active(True)
self.ui.text_destination_host.set_sensitive(True)
self.ui.text_machine_name.grab_focus()

def get_default_gateway(self):
"""Use netifaces module to get the default gateway."""
try:
import netifaces
gws = netifaces.gateways()
return gws['default'][netifaces.AF_INET][0]
except:
return ""

def on_radio_request_type_toggled(self, widget):
"""A Radio button was pressed"""
request_type_internet = self.ui.radio_request_internet.get_active()
request_type_mikrotik = self.ui.radio_request_mikrotik.get_active()
gateway = self.get_default_gateway()
# Check the request type
if request_type_internet:
# If there was the broadcast address it will be deleted
if self.do_get_destination() == BROADCAST_ADDRESS:
if self.do_get_destination() == BROADCAST_ADDRESS or self.do_get_destination() == gateway:
self.ui.text_destination_host.set_text('')
elif request_type_mikrotik:
if self.do_get_destination() == BROADCAST_ADDRESS or self.do_get_destination() == "":
# Set the default gateway as the default value
self.ui.text_destination_host.set_text(gateway)
else:
# For local request type the broadcast address will be used
self.ui.text_destination_host.set_text(BROADCAST_ADDRESS)
# Enable the destination fields accordingly to the request type
self.ui.label_destination_host.set_sensitive(request_type_internet)
self.ui.text_destination_host.set_sensitive(request_type_internet)
self.ui.label_destination_host.set_sensitive(request_type_internet or request_type_mikrotik)
self.ui.text_destination_host.set_sensitive(request_type_internet or request_type_mikrotik)

if request_type_mikrotik:
self.ui.image_computer.set_from_icon_name('network-modem', 72)
self.ui.label_destination_host.set_label(_("_Mikrotik Router:"))
else:
self.ui.image_computer.set_from_icon_name('computer', 72)
self.ui.label_destination_host.set_label(_("_Destination host:"))

self.ui.label_username.set_sensitive(request_type_mikrotik)
self.ui.text_username.set_sensitive(request_type_mikrotik)

self.ui.label_interface.set_sensitive(request_type_mikrotik)
self.ui.text_interface.set_sensitive(request_type_mikrotik)

self.ui.label_port_number.set_sensitive(not request_type_mikrotik)
self.ui.spin_port_number.set_sensitive(not request_type_mikrotik)

# Hide previous errors
self.ui.label_error.set_visible(False)
41 changes: 27 additions & 14 deletions gwakeonlan/ui/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@
get_treeview_selected_row,
process_events,
show_message_dialog_yesno,
wake_on_lan)
wake_on_lan,
split_credentials)
from gwakeonlan.import_ethers import ImportEthers
from gwakeonlan.localize import _, text
from gwakeonlan.settings import Settings
Expand All @@ -45,6 +46,7 @@
from gwakeonlan.ui.base import UIBase
from gwakeonlan.ui.arpcache import UIArpCache
from gwakeonlan.ui.detail import UIDetail
from gwakeonlan.ui.sshlogin import UISSHLogin
from gwakeonlan.ui.shortcuts import UIShortcuts

SECTION_WINDOW_NAME = 'main window'
Expand Down Expand Up @@ -77,6 +79,8 @@ def __init__(self, application, options):
self.model_machines = ModelMachines(self.ui.model)
# Load the others dialogs
self.detail = UIDetail(self.ui.window, self.settings, options)
# Load the SSH Login dialog
self.sshlogin = UISSHLogin(self.ui.window, self.settings, options)
# Complete initialization
self.startup()

Expand Down Expand Up @@ -155,22 +159,30 @@ def do_autotests(self):

def do_turn_on(self, treeiter):
"""Turn on the machine for the specified TreeIter"""
machine_name = self.model_machines.get_machine_name(treeiter=treeiter)
mac_address = self.model_machines.get_mac_address(treeiter=treeiter)
port_number = self.model_machines.get_port_number(treeiter=treeiter)
destination = self.model_machines.get_destination(treeiter=treeiter)
try:
wake_on_lan(mac_address=mac_address,
port_number=port_number,
destination=destination)
self.model_machines.set_icon(treeiter=treeiter,
value=self.icon_yes)
except OSError as error:
logging.error(f'Unable to turn on: {mac_address} '
f'through {destination} '
f'using port number {port_number}')
logging.error(error)
self.model_machines.set_icon(treeiter=treeiter,
value=self.icon_no)
username, router, interface = split_credentials(destination)
if username or interface:
# This is a mikrotik call
self.sshlogin.do_load_data(machine_name, mac_address, username, router, interface)
self.sshlogin.send_or_show()
else:
# this is a normal wake on lan
try:
wake_on_lan(mac_address=mac_address,
port_number=port_number,
destination=destination)
self.model_machines.set_icon(treeiter=treeiter,
value=self.icon_yes)
except OSError as error:
logging.error(f'Unable to turn on: {mac_address} '
f'through {destination} '
f'using port number {port_number}')
logging.error(error)
self.model_machines.set_icon(treeiter=treeiter,
value=self.icon_no)

def on_action_about_activate(self, widget):
"""Show the information dialog"""
Expand All @@ -196,6 +208,7 @@ def on_action_quit_activate(self, widget):
self.settings.save()
self.ui.window.destroy()
self.detail.destroy()
self.sshlogin.destroy()
self.application.quit()

def on_action_options_menu_activate(self, widget):
Expand Down
115 changes: 115 additions & 0 deletions gwakeonlan/ui/sshlogin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
##
# Project: gWakeOnLAN
# Description: Wake up your machines using Wake on LAN
# Author: Fabio Castelli (Muflone) <[email protected]>
# Copyright: 2009-2022 Fabio Castelli
# License: GPL-3+
# 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 <https://www.gnu.org/licenses/>.
##

import logging

from gi.repository import GLib
from gi.repository import Gtk

from gwakeonlan.localize import _
from gwakeonlan.ui.base import UIBase
from gwakeonlan.mikrotik import Mikrotik

SECTION_WINDOW_NAME = 'sshlogin'


class UISSHLogin(UIBase):
def __init__(self, parent, settings, options):
"""Prepare the dialog"""
logging.debug(f'{self.__class__.__name__} init')
super().__init__(filename='ssh-login.ui')
# Initialize members
self.parent = parent
self.settings = settings
self.options = options
# Mikrotik setup
self.mikrotik = Mikrotik()
# Load UI
self.load_ui()
# Complete initialization
self.startup()

def load_ui(self):
"""Load the interface UI"""
logging.debug(f'{self.__class__.__name__} load UI')
# Initialize titles and tooltips
self.set_titles()
# Set various properties
self.ui.SSHLogin.set_transient_for(self.parent)
# Load icon from file
self.load_image_file(self.ui.image_computer)
# Connect signals from the UI file to the functions with the same name
self.ui.connect_signals(self)

def startup(self):
"""Complete initialization"""
logging.debug(f'{self.__class__.__name__} startup')
# Restore the saved size and position
self.settings.restore_window_position(window=self.ui.SSHLogin,
section=SECTION_WINDOW_NAME)

def show(self):
"""Show the dialog"""
if self.options.autotest:
GLib.timeout_add(500, self.ui.SSHLogin.hide)
self.ui.label_error.set_property('visible', False)
self.ui.SSHLogin.set_title(_('Input your router\'s credentials'))
self.ui.SSHLogin.show_all()
response = self.ui.SSHLogin.run()
self.ui.SSHLogin.hide()
return response

def destroy(self):
"""Destroy the dialog"""
logging.debug(f'{self.__class__.__name__} destroy')
self.ui.SSHLogin.destroy()
self.ui.SSHLogin = None

def do_load_data(self, machine_name, mac_address, username, router, interface):
"""Load the fields with the specified values"""
self.ui.text_username.set_text(username or "")
self.ui.label_machine_name.set_label(machine_name or "")
self.ui.label_mac_address.set_label(mac_address or "")
self.ui.label_interface.set_label(interface or "")
self.ui.label_mikrotik_router.set_label(router or "")
self.ui.text_password.grab_focus()

def set_error(self, msg):
"""Set the error message of the first try"""
if msg != "":
self.ui.label_error.set_property('visible', True)
self.ui.label_error.set_label(f'<span foreground="red">{msg}</span>')
else:
self.ui.label_error.set_property('visible', False)

def send_or_show(self):
"""Try sending the request, if not possible to login, then show the dialog and ask for credentials"""
mac_address = self.ui.label_mac_address.get_label()
username = self.ui.text_username.get_text()
password = self.ui.text_password.get_text()
router = self.ui.label_mikrotik_router.get_label()
interface = self.ui.label_interface.get_label()
self.set_error("")
sent = self.mikrotik.wol(mac_address, username, router, interface, password)
if sent is not True:
self.set_error(sent)
if self.show() == Gtk.ResponseType.APPLY:
self.send_or_show()

Loading