Ρυθμίσεις Samba με 
γραφικό περιβάλλον

Βασίλης Κούλης
T03197

Στόχοι

  • Απόκτηση εμπειρίας στην ανάπτυξη κώδικα με γραφικό περιβάλλον
  • Δημιουργία εφαρμογής χρήσιμης  σε προσωπικό επίπεδο αλλά και γενικότερα
  • Ανάπτυξη πρωτότυπης εφαρμογής
  • Δυνατότητα λειτουργίας σε όλες τις πλατφόρμες (cross-plattform)

Samba

Το Samba φτιάχτηκε από τον Andrew Tridgell για διαμοιρασμό αρχείων και συσκευών μεταξύ των Windows και των Unix το 1991.

Το πρόγραμμα διατίθεται δωρεάν με άδεια GPLv3 και είναι γραμμένο σε C, C++ και σε Python

Samba Ιστορία

Οι πιο σημαντικές εκδώσεις είναι οι ακόλουθες:

  • v0.1-v1.0 
    Με reverse engineering μπόρεσε να διαβάζει αρχεία των Windows
  • v1.5
    Σε συνεργασία με την Microsoft φτιάχτηκε client και server
  • v3.0
    Σύνδεση σε Domain
  • v3.4
    Ήταν η πιο σταθερή έκδοση
  • v4
    Πλήρη υποστήριξη Domain

Samba Πρωτόκολλα

  • NetBios μέσω TCP/IP (NBT)

  • Server Message Block (SMB)

  • Common Internet File System (CIFS)

  • Microsoft Remote Procedure Call (MSRPC)

  • Windows Internet Name Service (WINS)

  • Security Accounts Manager (SAM) database

  • Local Security Authority (LSA) service

  • Workgroup

  • Active Directory (AD)

GTK+

Το GTK+ είναι μια αντικειμενοστραφής εργαλειοθήκη widget γραμμένη σε C για την δημιουργία γραφικού περιβάλλοντος σε οποιαδήποτε πλατφόρμα.

Φτιάχτηκε το 1998 από τον Spancer Kimball με άδεια ανοιχτού λογισμικού LGPL.

Python & GTK+

Ξεκίνησα χρησιμοποιώντας το Quickly που είναι ένα πρόγραμμα της Canonical για εύκολο σχεδιασμό και επεξεργασία κώδικα με glade και python. Επίσης μπορεί να δημοσιεύσει εύκολα το project χρησιμοποιώντας το GNU Bazaar ή το Launchpad.

Λειτουργικότητα Quickly

Οι βασικές εντολές του quickly πακέτου είναι οι ακόλουθες:

  • quickly create ubuntu-application foo
    Φτιάχνει την δομή των φακέλων
  • quickly edit
    Ανοίγει τον προεπιλεγμένο editor
  • quickly design
    Ανοίγει το glade
  • quickly run
    Τρέχει την εφαρμογή

Quickly Σχεδιασμός

Το Glade δημιουργεί ένα αρχείο xml.

<?xml version="1.0" encoding="UTF-8"?>
<interface>
  <!-- interface-requires gtk+ 3.0 -->
  <object class="GtkDialog" id="UserOptions">
    <property name="transient_for">samba_config_window</property>
    <child internal-child="vbox">
      <object class="GtkBox" id="dialog-vbox1">
        <child internal-child="action_area">
          <object class="GtkButtonBox" id="dialog-action_area1">
            <property name="can_focus">False</property>
            <property name="layout_style">end</property>
            <child>
              <object class="GtkButton" id="UserButtonOK">
                <property name="label" translatable="yes">OK</property>
                <property name="visible">True</property>
                <property name="can_focus">True</property>
                <property name="receives_default">True</property>
              </object>
              <packing>
                <property name="expand">False</property>
                <property name="fill">True</property>
                <property name="position">0</property>
              </packing>
            </child>
            <child>
              <object class="GtkButton" id="UserButtonCancel">
                <property name="label" translatable="yes">Cancel</property>
                <property name="visible">True</property>
                <property name="can_focus">True</property>
                <property name="receives_default">True</property>
                <accelerator key="w" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
              </object>
              <packing>
                <property name="expand">False</property>
                <property name="fill">True</property>
                <property name="position">1</property>
              </packing>
            </child>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="pack_type">end</property>
            <property name="position">0</property>
          </packing>
        </child>
        <child>
          <object class="GtkGrid" id="grid3">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <child>
              <object class="GtkLabel" id="UserLabel">
                <property name="visible">True</property>
                <property name="can_focus">False</property>
                <property name="xpad">15</property>
                <property name="label" translatable="yes">UserName</property>
              </object>
              <packing>
                <property name="left_attach">0</property>
                <property name="top_attach">0</property>
                <property name="width">1</property>
                <property name="height">1</property>
              </packing>
            </child>
            <child>
              <object class="GtkLabel" id="PasswordLabel">
                <property name="visible">True</property>
                <property name="can_focus">False</property>
                <property name="xpad">15</property>
                <property name="label" translatable="yes">Password</property>
              </object>
              <packing>
                <property name="left_attach">0</property>
                <property name="top_attach">1</property>
                <property name="width">1</property>
                <property name="height">1</property>
              </packing>
            </child>
            <child>
              <object class="GtkEntry" id="UserEntry">
                <property name="visible">True</property>
                <property name="can_focus">True</property>
                <property name="hexpand">True</property>
                <property name="invisible_char">•</property>
              </object>
              <packing>
                <property name="left_attach">1</property>
                <property name="top_attach">0</property>
                <property name="width">1</property>
                <property name="height">1</property>
              </packing>
            </child>
            <child>
              <object class="GtkEntry" id="PasswordEntry">
                <property name="visible">True</property>
                <property name="can_focus">True</property>
                <property name="hexpand">True</property>
                <property name="invisible_char">•</property>
                <property name="input_purpose">password</property>
              </object>
              <packing>
                <property name="left_attach">1</property>
                <property name="top_attach">1</property>
                <property name="width">1</property>
                <property name="height">1</property>
              </packing>
            </child>
          </object>
          <packing>
            <property name="expand">True</property>
            <property name="fill">True</property>
            <property name="position">1</property>
          </packing>
        </child>
      </object>
    </child>
    <action-widgets>
      <action-widget response="0">UserButtonOK</action-widget>
      <action-widget response="0">UserButtonCancel</action-widget>
    </action-widgets>
  </object>
</interface>

Εφαρμογές Samba GUI

Αντίστοιχες εφαρμογές για διαχείριση του samba είναι οι παρακάτω:

  • GADMIN-SAMBA
    Αρκετά πολύπλοκη αλλά με όλες τις δυνατότητες που χρησιμοποιεί το Samba
  • System Config Samba
    Πολύ απλή, αλλά με πολύ λιγότερες δυνατότητες

Η εφαρμογή μου

Η εφαρμογή αποτελείται από 4 παράθυρα:

Κλάσεις

Υπάρχουν 3 κλάσεις:

  • Window
    Ξεκινάει την εφαρμογή και ανάλογα με το trigger που χρειάζεται καλεί την ανάλογη μέθοδο
  • WinFunc
    Περιέχει όλες τις μεθόδους που χρησιμοποιούνται
  • Builder
    Διαβάζει το αρχείο glade και το μετατρέπει σε python μεθόδους

Class Window

class Window(WinFunc):
    '''Main Class for Signals'''
    def __init__(self):
        self.MainWindow = builder("SambaConfigWindow.glade","samba_config_window")
        self.ui = self.MainWindow.get_ui(self)

        PASSWORD = "1"
        WinFunc.Initialize(self, PASSWORD)

#Window Signals
    def on_samba_config_window_destroy(self, widget, data=None):
        Gtk.main_quit()
    def on_mnu_close_activate(self, widget, data=None):
        Gtk.main_quit()
    def on_ShareOptions_delete_event(self, widget, data=None):
        self.ui.ShareOptions.hide()
        return True
    def on_BrowseDialog_delete_event(self, widget, data=None):
        self.ui.BrowseDialog.hide()
        return True
    def on_UserOptions_delete_event(self, widget, data=None):
        self.ui.UserOptions.hide()
        return True


#Toolbar Main Buttons
    def on_SaveButton_clicked(self, widget, data=None):
        WinFunc.SaveChanges(self)

    def on_RefreshButton_clicked(self, widget, data=None):
        WinFunc.Initialize(self, self.ROOTPASS)


#Shares Tab
    def on_EditButton_clicked(self, widget, data=None):
        WinFunc.EditShares(self)

    def on_DeleteButton_clicked(self, widget, data=None):
        WinFunc.DeleteShares(self)

    def on_AddButton_clicked(self, widget, data=None):
        WinFunc.AddShares(self)

    def on_SharesTree_focus_out_event(self,widget,gpoint):
        self.ui.EditButton.set_sensitive(False)
        self.ui.DeleteButton.set_sensitive(False)
        self.ui.AddButton.set_sensitive(False)

    def on_SharesTree_focus_in_event(self,widget,gpoint):
        self.ui.EditButton.set_sensitive(True)
        self.ui.DeleteButton.set_sensitive(True)
        self.ui.AddButton.set_sensitive(True)
        WinFunc.WriteShares(self)

#Users Tab
    def on_AddUserButton_clicked(self,widget,data=None):
        WinFunc.AddUser(self)

    def on_EditUserButton_clicked(self,widget,data=None):
        WinFunc.EditUser(self)

    def on_DeleteUserButton_clicked(self,widget,data=None):
        WinFunc.DeleteUser(self)


#Shares Window
    def on_SharesCheckPublic_toggled(self, widget, data=None):
        if (self.ui.SharesCheckPublic.get_active()==True):
            self.ui.TabsNotebook.set_show_tabs(False)
        else:
            self.ui.TabsNotebook.set_show_tabs(True)

    def on_SharesButtonOK_clicked(self, widget, data=None):
        WinFunc.ConfirmShareChanges(self)

    def on_SharesButtonBrowse_clicked(self, widget, data=None):
        self.MainWindow.ShowWindow("BrowseDialog")
        Directory = self.ui.SharesEntryDirectory.get_text()
        if(Directory == ""):
            self.ui.BrowseDialog.set_current_folder("/home")
        else:
            self.ui.BrowseDialog.set_current_folder(Directory)

    def on_SharesButtonCancel_clicked(self, widget, data=None):
        self.ui.ShareOptions.hide()

#BrowseDialog Window
    def on_BrowseOK_clicked(self, widget, data=None):
        try:
            self.ui.SharesEntryDirectory.set_text(self.ui.BrowseDialog.get_current_folder())
            self.ui.BrowseDialog.hide()
        except:
            output = Popen(["notify-send", "This is not a Directory"]).communicate()

    def on_BrowseCancel_clicked(self, widget, data=None):
        self.ui.BrowseDialog.hide()

#Users Window
    def on_UserButtonOK_clicked(self, widget, data=None):
        WinFunc.ConfirmUserChanges(self)

    def on_UserButtonCancel_clicked(self, widget, data=None):
        self.ui.UserOptions.hide()

Class WinFunc


class WinFunc():

    def Initialize(self, ROOTPASS):
        '''Define Variables and Call Methods for initialization'''
        # DEFINE Variables
        ...
        # CALL Methods
        ...

    # Files-Lists
    def __CreateSMBLIST(self):
        '''Private Method to READ the smb config file and write to SMBLIST with the proper names'''
        ...

    def __FixSynonyms(self):
        '''Fix the names in the SMBLIST'''
        ...

    def __CreateSharesList(self):
        ''' Search in the SMBLIST for shares and create a new list, SharesList, to store them '''
        ...
        # Append items in List
        ...

    def __WriteConfig(self):
        ''' Write the whole smb config file in the Configuration Tab of the application GUI'''
        ...

    # Settings Tab
    def __GetProperties(self, search):
        ''' Search in the SMBLIST to for specific configuration '''
        ...

    def __PrintShareExists(self):
        ''' Search the SMBLIST for printer share '''
        ...

    # Shares Tab
    def WriteShares(self):
        ''' Write all the shares from the SharesList to Shares Tab in the application GUI '''
        ...

    def DeleteShares(self):
        ''' Get the selected row and delete it from Shares Tab and from SharesList '''
        ...

    def AddShares(self):
        ''' Open new empty Window for share '''
        ...

    def EditShares(self):
        ''' Get the selected share and open a new share Window based on the SharesList '''
        ...

    def __OpenShares(self, Name, Directory, Description, Access, Permissions, Title):
        ''' Write the properties to the Shares Window '''
        ...

    def ConfirmShareChanges(self):
        # Get Options from the window
        ...

        # Get Selected Users who have access to the shared directory
        ...

        # Check if Everything is Fine
        ...

        # Update the SharesList with the new Options
        ...

    # Users Tab
    def __WriteUsers(self):
        ''' Write all the Users from the the output of the pdbedit to Users Tab in the application GUI '''
        ...

    def DeleteUser(self):
        ''' Delete the selected User from the application GUI and from the system '''
        ...

    def AddUser(self):
        ''' Open new empty Window for UserName and Password '''
        ...

    def EditUser(self):
        ''' Get the selected User and open a new User Window to edit the user password '''
        ...

    def __OpenUsers(self, UserName, Password, Title):
        ''' Write the properties to the Users Window '''
        ...

    def ConfirmUserChanges(self):
        # Get Options from the window
        ...

    def ChangeUser(self, UserName, Password):
        ''' Used to add/edit the user of the system '''
        # If the User exists then Edit the password only
        ...

        # Add User to the system
        ...

        # Change Password of the User
        # If no change is made to the window's password
        ...

    # Ending
    def SaveChanges(self):
        ...

        # Write Config from template file
        ...

        # Write Shares
        ...

        # Save the list to the config file
        ...

Class builder

class builder():
    def __init__(self,filename,window):
        self.widgets = {}
        self.widgets = {}
        self.builder = Gtk.Builder()
        self.builder.add_from_file(filename)
        
        self.ShowWindow(window)
        
        tree = ElementTree()
        tree.parse(filename)
        
        ele_widgets = tree.getiterator("object")
        for ele_widget in ele_widgets:
            name = ele_widget.attrib['id']
            widget = self.builder.get_object(name)

            self.widgets[name] = widget
        print dir(widget)

    def ShowWindow(self,window):
        self.builder.get_object(window).show()
    
    def get_ui(self, callback_obj):
        '''Return the attributes of the widgets and Connects to Handler'''
        #Get all methods of class window and write them to a dictionary 
        methods = []
        for k in dir(callback_obj):
            try:
                attr = getattr(callback_obj, k)
            except:
                continue
            if inspect.ismethod(attr):
                methods.append((k, attr))
        methods.sort()
        dict_methods = dict(methods)
        callback_handler_dict = {}
        callback_handler_dict.update(dict_methods)


        for (widget_name, widget) in self.widgets.items():
            setattr(self, widget_name, widget) #Return the attribute
            signal_names = self.__get_signals(widget) #Get a list of the widget signals

            for signal_name in signal_names:
                signal_name = signal_name.replace("-", "_")
                handler_names = ["on_%s_%s" % (widget_name, signal_name)]
                
                #For every method of the window, connect the signals
                for handler_name in handler_names:
                    target = handler_name in callback_handler_dict.keys()
                    if target:
                        widget.connect(signal_name, callback_handler_dict[handler_name])

        return self


    def __get_signals(self, widget):
        '''Return the signals of the widget'''
        signal_ids = []
        try:
            widget_type = type(widget)
            while widget_type:
                signal_ids.extend(GObject.signal_list_ids(widget_type))
                widget_type = GObject.type_parent(widget_type)
        except RuntimeError:
            pass
        signal_names = [GObject.signal_name(sid) for sid in signal_ids]
        return signal_names

Manual - Settings

Βασικές ρυθμίσεις

Manual - Shares

Επιλέγει ποιους φακέλους να μοιράζεται

Manual - Users

Δημιουργεί, διαγράφει χρήστες

Manual - Configuration

Εμφανίζεται το τελικό αποτέλεσμα που θα αποθηκευτεί

Επίλογος

  • Όλοι οι στόχοι επιτεύχθηκαν εκτός από το να είναι ανεξαρτήτου πλατφόρμας λόγω της ανάγκης χρησιμοποίησης εντολών λειτουργικού.
  • Θα μπορούσα να το εμπλουτίσω σε δυνατότητες, ωστόσο θα έχανε την ευκολία χρήσης που είναι ζητούμενο.