├── CMakeLists.txt ├── README.md ├── data ├── adddistrodialog.glade ├── addfolderdialog.glade ├── addhostdialog.glade ├── adduserdialog.glade ├── clonedistrodialog.glade ├── edithostdialog.glade ├── edituserdialog.glade ├── firstuse.glade ├── ldapsettingsdialog.glade ├── org.raspberrypi.piserver.policy ├── piserver.desktop ├── piserver.desktop.in └── piserver.glade ├── debian ├── changelog ├── compat ├── control ├── copyright ├── install ├── piserver_ssh.service ├── piserver_sshd_config ├── postinst ├── postrm ├── rules └── source │ └── format ├── macros ├── BuildTargetScript.cmake ├── CompileGResources.cmake ├── GenerateGXML.cmake └── GlibCompileResourcesSupport.cmake ├── po ├── LINGUAS ├── POTFILES.in ├── en_GB.po └── piserver.pot ├── scripts ├── 50-piserver-dhcpcd-hook ├── chroot_image.sh ├── convert.sh ├── convert_chrooted_cmds ├── convert_folder.sh ├── mkhomedir-piserver └── postinstall.sh └── src ├── abstractadddistro.cpp ├── abstractadddistro.h ├── abstractaddhost.cpp ├── abstractaddhost.h ├── abstractadduser.cpp ├── abstractadduser.h ├── activediscovery.cpp ├── activediscovery.h ├── adddistrodialog.cpp ├── adddistrodialog.h ├── addfolderdialog.cpp ├── addfolderdialog.h ├── addhostdialog.cpp ├── addhostdialog.h ├── adduserdialog.cpp ├── adduserdialog.h ├── clonedistrodialog.cpp ├── clonedistrodialog.h ├── config.h ├── csv └── csv.h ├── dependenciesinstallthread.cpp ├── dependenciesinstallthread.h ├── dhcp.h ├── dhcpanalyzer.cpp ├── dhcpanalyzer.h ├── dhcpclient.cpp ├── dhcpclient.h ├── distribution.cpp ├── distribution.h ├── downloadextractthread.cpp ├── downloadextractthread.h ├── downloadthread.cpp ├── downloadthread.h ├── edithostdialog.cpp ├── edithostdialog.h ├── edituserdialog.cpp ├── edituserdialog.h ├── exportdistrodialog.cpp ├── exportdistrodialog.h ├── host.cpp ├── host.h ├── importosthread.cpp ├── importosthread.h ├── installwizard.cpp ├── installwizard.h ├── json └── json.hpp ├── ldapsettingsdialog.cpp ├── ldapsettingsdialog.h ├── main.cpp ├── mainwindow.cpp ├── mainwindow.h ├── piserver.cpp ├── piserver.h ├── stpanalyzer.cpp ├── stpanalyzer.h ├── user.cpp └── user.h /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(piserver) 2 | cmake_minimum_required(VERSION 3.1) 3 | 4 | set (CMAKE_CXX_STANDARD 17) 5 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -Wno-psabi") 6 | 7 | find_package(Gettext) 8 | set(THREADS_PREFER_PTHREAD_FLAG ON) 9 | find_package(Threads REQUIRED) 10 | find_package(CURL REQUIRED) 11 | find_package(LibArchive REQUIRED) 12 | find_package(PkgConfig) 13 | pkg_check_modules(GTKMM REQUIRED gtkmm-3.0) 14 | link_directories(${GTKMM_LIBRARY_DIRS}) 15 | include_directories(${GTKMM_INCLUDE_DIRS}) 16 | 17 | include(CheckIncludeFile) 18 | check_include_file(ldap.h HAVE_LDAP_H) 19 | find_library(LDAP_LIBRARY ldap) 20 | 21 | if(NOT HAVE_LDAP_H OR NOT LDAP_LIBRARY) 22 | message( FATAL_ERROR "Missing libldap. Try 'sudo apt install libldap2-dev'" ) 23 | endif() 24 | 25 | find_library(CAP_LIBRARY cap) 26 | check_include_file(sys/capability.h HAVE_CAPABILITY_H) 27 | if(NOT HAVE_CAPABILITY_H OR NOT CAP_LIBRARY) 28 | message( FATAL_ERROR "Missing libcap. Try 'sudo apt install libcap-dev'" ) 29 | endif() 30 | 31 | # for crypt_r() 32 | find_library(CRYPT_LIBRARY crypt) 33 | 34 | # for resolving SRV DNS records 35 | find_library(RESOLV_LIBRARY resolv) 36 | 37 | # test if we need libatomic 38 | include(CheckCXXSourceCompiles) 39 | check_cxx_source_compiles(" 40 | #include 41 | #include 42 | int main() { 43 | std::atomic x; 44 | x = 1; 45 | return (int) x; 46 | }" 47 | atomicbuiltin) 48 | 49 | if (NOT atomicbuiltin) 50 | find_library(ATOMIC_LIBRARY NAMES atomic libatomic.so.1) 51 | if (NOT ATOMIC_LIBRARY) 52 | message( FATAL_ERROR "Missing libatomic while architecture does need it" ) 53 | endif() 54 | endif() 55 | 56 | aux_source_directory(src SRC_LIST) 57 | add_executable(${PROJECT_NAME} ${SRC_LIST} resources.c) 58 | target_link_libraries(${PROJECT_NAME} ${GTKMM_LIBRARIES} ${LDAP_LIBRARY} 59 | ${CRYPT_LIBRARY} ${RESOLV_LIBRARY} ${CURL_LIBRARY} ${LibArchive_LIBRARIES} ${CAP_LIBRARY} ${ATOMIC_LIBRARY} Threads::Threads -lstdc++fs) 60 | 61 | # Languages 62 | 63 | if (GETTEXT_FOUND) 64 | file(GLOB LANGUAGES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} po/*.po) 65 | GETTEXT_CREATE_TRANSLATIONS(po/piserver.pot ALL ${LANGUAGES}) 66 | add_definitions(-DLOCALEDIR="/usr/share/locale") 67 | endif() 68 | 69 | # Resources 70 | 71 | list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/macros) 72 | include(GlibCompileResourcesSupport) 73 | 74 | set(RESOURCE_LIST 75 | data/firstuse.glade 76 | data/piserver.glade 77 | data/addhostdialog.glade 78 | data/edithostdialog.glade 79 | data/adduserdialog.glade 80 | data/edituserdialog.glade 81 | data/adddistrodialog.glade 82 | data/addfolderdialog.glade 83 | data/clonedistrodialog.glade 84 | data/ldapsettingsdialog.glade 85 | ) 86 | 87 | compile_gresources(RESOURCE_FILE 88 | XML_OUT 89 | TYPE EMBED_C 90 | RESOURCES ${RESOURCE_LIST}) 91 | 92 | add_custom_target(resource ALL DEPENDS ${RESOURCE_FILE}) 93 | 94 | # Installation 95 | 96 | install(TARGETS piserver DESTINATION bin) 97 | install(FILES data/piserver.desktop DESTINATION share/applications) 98 | install(FILES data/org.raspberrypi.piserver.policy DESTINATION share/polkit-1/actions) 99 | install(DIRECTORY DESTINATION /var/lib/piserver) 100 | install(DIRECTORY DESTINATION /var/lib/piserver/os) 101 | install(DIRECTORY DESTINATION /var/lib/piserver/os/shared) 102 | install(DIRECTORY DESTINATION /var/lib/piserver/tftproot) 103 | install(DIRECTORY DESTINATION /var/lib/piserver/scripts) 104 | install(PROGRAMS scripts/postinstall.sh scripts/convert.sh scripts/convert_folder.sh scripts/chroot_image.sh DESTINATION /var/lib/piserver/scripts) 105 | install(FILES scripts/convert_chrooted_cmds scripts/mkhomedir-piserver DESTINATION /var/lib/piserver/scripts) 106 | install(FILES scripts/50-piserver-dhcpcd-hook DESTINATION /lib/dhcpcd/dhcpcd-hooks) 107 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # piserver 2 | Raspberry Pi Server wizard to serve Raspbian to network booting Pis 3 | 4 | ## How to rebuild piserver 5 | 6 | ### Get dependencies 7 | 8 | On Raspbian install the build dependencies: 9 | 10 | ``` 11 | sudo apt-get install build-essential devscripts debhelper cmake libldap2-dev libgtkmm-3.0-dev libarchive-dev libcurl4-openssl-dev libcap-dev intltool git 12 | ``` 13 | 14 | If not using a Pi (or other armhf device), you also need the following runtime dependencies: 15 | 16 | ``` 17 | sudo apt-get install binfmt-support qemu-user-static 18 | ``` 19 | 20 | 21 | ### Get the source 22 | 23 | ``` 24 | git clone --depth 1 https://github.com/raspberrypi/piserver.git 25 | ``` 26 | 27 | ### Build the Debian package 28 | 29 | ``` 30 | cd piserver 31 | debuild -uc -us 32 | ``` 33 | 34 | debuild will compile everything, create a .deb package and put it in the parent directory. 35 | Can install it with dpkg: 36 | 37 | ``` 38 | cd .. 39 | sudo dpkg -i piserver*.deb 40 | ``` 41 | 42 | ### Extra dependencies if NOT running Raspbian or "Debian Stretch with Raspberry Pi desktop" 43 | 44 | #### dnsmasq 45 | 46 | Piserver requires dnsmasq version 2.77 or later. 47 | If your Linux distribution comes with an older version, compile the latest version from source: 48 | 49 | ``` 50 | sudo apt-get install libnetfilter-conntrack-dev libidn11-dev libgmp-dev nettle-dev liblua5.2-dev 51 | git clone --depth 1 http://thekelleys.org.uk/git/dnsmasq.git 52 | cd dnsmasq 53 | debuild -uc -us 54 | cd .. 55 | sudo dpkg -i dnsmasq-base_*.deb dnsmasq_*.deb 56 | ``` 57 | 58 | If you are running Raspbian this is not necessary. Although it is using an older dnsmasq version as well, it has been patched to include the newer features used (--dhcp-reply-delay and tftp-unique-root=mac options). 59 | 60 | #### IP update hook 61 | 62 | It is expected that the computer that is running piserver: 63 | 64 | * is configured to use a static IP-address that never changes. 65 | * -OR- uses dhcpcd as network manager. In which case it will notify piserver automatically if your server's IP ever changes. 66 | * -OR- that you configure your network manager program (e.g. using a ifup/networkd-dispatcher hook script) to execute "piserver --update-ip" every time the system obtains a new IP lease. 67 | -------------------------------------------------------------------------------- /data/addfolderdialog.glade: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | False 7 | Create shared folder 8 | dialog 9 | 10 | 11 | False 12 | vertical 13 | 2 14 | 15 | 16 | False 17 | 6 18 | 6 19 | end 20 | 21 | 22 | gtk-cancel 23 | True 24 | True 25 | True 26 | True 27 | 28 | 29 | True 30 | True 31 | 0 32 | 33 | 34 | 35 | 36 | gtk-ok 37 | True 38 | False 39 | True 40 | True 41 | True 42 | 43 | 44 | True 45 | True 46 | 1 47 | 48 | 49 | 50 | 51 | False 52 | False 53 | 0 54 | 55 | 56 | 57 | 58 | True 59 | False 60 | 10 61 | 10 62 | 10 63 | 10 64 | 9 65 | 66 | 67 | True 68 | False 69 | Name of shared folder 70 | 0 71 | 72 | 73 | 0 74 | 0 75 | 76 | 77 | 78 | 79 | True 80 | False 81 | Owner 82 | 0 83 | 84 | 85 | 0 86 | 1 87 | 88 | 89 | 90 | 91 | True 92 | False 93 | Permissions 94 | 0 95 | 0 96 | 97 | 98 | 0 99 | 2 100 | 101 | 102 | 103 | 104 | True 105 | True 106 | 107 | 108 | 1 109 | 0 110 | 111 | 112 | 113 | 114 | True 115 | True 116 | 117 | 118 | 1 119 | 1 120 | 121 | 122 | 123 | 124 | True 125 | False 126 | - Owner can edit the contents 127 | of the folder on this computer. 128 | - All users have read-only access 129 | to the folder on the terminals. 130 | 131 | True 132 | 0.01 133 | 0 134 | 135 | 136 | 1 137 | 2 138 | 139 | 140 | 141 | 142 | False 143 | True 144 | 1 145 | 146 | 147 | 148 | 149 | 150 | button1 151 | okbutton 152 | 153 | 154 | 155 | -------------------------------------------------------------------------------- /data/addhostdialog.glade: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | False 21 | Add Clients 22 | dialog 23 | 24 | 25 | False 26 | vertical 27 | 10 28 | 29 | 30 | False 31 | 6 32 | 6 33 | end 34 | 35 | 36 | gtk-cancel 37 | True 38 | True 39 | True 40 | True 41 | 42 | 43 | True 44 | True 45 | 0 46 | 47 | 48 | 49 | 50 | gtk-ok 51 | True 52 | False 53 | True 54 | True 55 | True 56 | 57 | 58 | True 59 | True 60 | 1 61 | 62 | 63 | 64 | 65 | False 66 | False 67 | 0 68 | 69 | 70 | 71 | 72 | True 73 | False 74 | 10 75 | 10 76 | 10 77 | 10 78 | vertical 79 | 80 | 81 | True 82 | False 83 | Ensure all the Pis you want to use are set up to boot from the network, 84 | connected to Ethernet and powered on. They will appear in the list below. 85 | (Note that any Pis already registered do not appear in the list.) 86 | 87 | Uncheck the box next to any Pis you do not want to use with this server. 88 | 89 | Select the operating system to run on these Pis from the drop-down box. 90 | 91 | True 92 | 0 93 | 94 | 95 | False 96 | True 97 | 0 98 | 99 | 100 | 101 | 102 | True 103 | True 104 | True 105 | in 106 | 150 107 | 108 | 109 | True 110 | True 111 | immediate 112 | hoststore 113 | 0 114 | both 115 | 116 | 117 | 118 | 119 | 120 | 40 121 | 122 | 123 | 124 | 0 125 | 126 | 127 | 128 | 129 | 130 | 131 | MAC address of Raspberry Pi 132 | 133 | 134 | 135 | 1 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | False 145 | True 146 | 1 147 | 148 | 149 | 150 | 151 | True 152 | False 153 | 10 154 | 155 | 156 | True 157 | False 158 | Description of clients (optional): 159 | 0 160 | 161 | 162 | 0 163 | 0 164 | 165 | 166 | 167 | 168 | True 169 | True 170 | True 171 | 172 | 173 | 1 174 | 0 175 | 176 | 177 | 178 | 179 | True 180 | False 181 | Operating system: 182 | 0 183 | 184 | 185 | 0 186 | 1 187 | 188 | 189 | 190 | 191 | True 192 | False 193 | distrostore 194 | 195 | 196 | 197 | 0 198 | 199 | 200 | 201 | 202 | 1 203 | 1 204 | 205 | 206 | 207 | 208 | False 209 | True 210 | 2 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | button1 219 | okbutton 220 | 221 | 222 | 223 | -------------------------------------------------------------------------------- /data/clonedistrodialog.glade: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | False 7 | Clone operating system 8 | dialog 9 | 10 | 11 | False 12 | vertical 13 | 9 14 | 15 | 16 | False 17 | end 18 | 19 | 20 | gtk-cancel 21 | True 22 | True 23 | True 24 | True 25 | 0.43000000715255737 26 | 27 | 28 | True 29 | True 30 | 0 31 | 32 | 33 | 34 | 35 | gtk-ok 36 | True 37 | False 38 | True 39 | True 40 | True 41 | 0.55000001192092896 42 | 43 | 44 | True 45 | True 46 | 1 47 | 48 | 49 | 50 | 51 | False 52 | False 53 | 0 54 | 55 | 56 | 57 | 58 | True 59 | False 60 | 9 61 | 62 | 63 | True 64 | False 65 | Operating system: 66 | 0 67 | 68 | 69 | 0 70 | 0 71 | 72 | 73 | 74 | 75 | True 76 | False 77 | New name: 78 | 0 79 | 80 | 81 | 0 82 | 1 83 | 84 | 85 | 86 | 87 | True 88 | False 89 | Raspbian 90 | 0 91 | 92 | 93 | 1 94 | 0 95 | 96 | 97 | 98 | 99 | True 100 | True 101 | True 102 | 30 103 | 104 | 105 | 1 106 | 1 107 | 108 | 109 | 110 | 111 | False 112 | True 113 | 1 114 | 115 | 116 | 117 | 118 | 119 | button1 120 | okbutton 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /data/edithostdialog.glade: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | False 13 | Edit Client 14 | dialog 15 | 16 | 17 | False 18 | vertical 19 | 2 20 | 21 | 22 | False 23 | baseline 24 | 6 25 | 6 26 | end 27 | 28 | 29 | gtk-cancel 30 | True 31 | True 32 | True 33 | True 34 | 35 | 36 | True 37 | True 38 | 0 39 | 40 | 41 | 42 | 43 | gtk-ok 44 | True 45 | True 46 | True 47 | True 48 | 49 | 50 | True 51 | True 52 | 1 53 | 54 | 55 | 56 | 57 | False 58 | False 59 | 0 60 | 61 | 62 | 63 | 64 | True 65 | False 66 | 10 67 | 10 68 | 10 69 | 10 70 | 71 | 72 | True 73 | False 74 | 5 75 | 10 76 | True 77 | 78 | 79 | True 80 | False 81 | MAC address: 82 | 0 83 | 84 | 85 | 0 86 | 0 87 | 88 | 89 | 90 | 91 | True 92 | False 93 | Description: 94 | 0 95 | 96 | 97 | 0 98 | 1 99 | 100 | 101 | 102 | 103 | True 104 | False 105 | Operating system: 106 | 0 107 | 108 | 109 | 0 110 | 2 111 | 112 | 113 | 114 | 115 | True 116 | False 117 | 11:22:33:44:55:66 118 | 0 119 | 120 | 121 | 1 122 | 0 123 | 124 | 125 | 126 | 127 | True 128 | True 129 | 130 | 131 | 1 132 | 1 133 | 134 | 135 | 136 | 137 | True 138 | False 139 | distrostore 140 | 141 | 142 | 143 | 0 144 | 145 | 146 | 147 | 148 | 1 149 | 2 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | button2 160 | button1 161 | 162 | 163 | 164 | -------------------------------------------------------------------------------- /data/edituserdialog.glade: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | False 7 | Edit user 8 | dialog 9 | 10 | 11 | 12 | 13 | 14 | False 15 | vertical 16 | 2 17 | 18 | 19 | False 20 | 6 21 | 6 22 | end 23 | 24 | 25 | gtk-cancel 26 | True 27 | True 28 | True 29 | True 30 | 31 | 32 | True 33 | True 34 | 0 35 | 36 | 37 | 38 | 39 | gtk-ok 40 | True 41 | True 42 | True 43 | True 44 | 45 | 46 | True 47 | True 48 | 1 49 | 50 | 51 | 52 | 53 | False 54 | False 55 | 0 56 | 57 | 58 | 59 | 60 | True 61 | False 62 | 10 63 | 10 64 | 10 65 | 10 66 | 67 | 68 | True 69 | False 70 | 5 71 | 10 72 | True 73 | 74 | 75 | True 76 | False 77 | User: 78 | 0 79 | 80 | 81 | 0 82 | 0 83 | 84 | 85 | 86 | 87 | True 88 | False 89 | 0 90 | 91 | 92 | 1 93 | 0 94 | 95 | 96 | 97 | 98 | Show passwords 99 | True 100 | True 101 | False 102 | 0 103 | True 104 | 105 | 106 | 0 107 | 4 108 | 109 | 110 | 111 | 112 | True 113 | False 114 | Confirm password: 115 | 0 116 | 117 | 118 | 0 119 | 3 120 | 121 | 122 | 123 | 124 | True 125 | True 126 | False 127 | 128 | 129 | 1 130 | 3 131 | 132 | 133 | 134 | 135 | True 136 | False 137 | Password: 138 | 0 139 | 140 | 141 | 0 142 | 2 143 | 144 | 145 | 146 | 147 | True 148 | True 149 | False 150 | password 151 | 152 | 153 | 1 154 | 2 155 | 156 | 157 | 158 | 159 | True 160 | False 161 | Description: 162 | 0 163 | 164 | 165 | 0 166 | 1 167 | 168 | 169 | 170 | 171 | True 172 | True 173 | 174 | 175 | 1 176 | 1 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | False 187 | True 188 | 1 189 | 190 | 191 | 192 | 193 | 194 | button2 195 | okbutton 196 | 197 | 198 | 199 | -------------------------------------------------------------------------------- /data/ldapsettingsdialog.glade: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | [all groups allowed] 21 | 22 | 23 | 24 | 25 | False 26 | Advanced LDAP settings 27 | 640 28 | 480 29 | dialog 30 | 31 | 32 | 33 | 34 | 35 | False 36 | vertical 37 | 2 38 | 39 | 40 | False 41 | 4 42 | end 43 | 44 | 45 | gtk-cancel 46 | True 47 | True 48 | True 49 | True 50 | 51 | 52 | True 53 | True 54 | 0 55 | 56 | 57 | 58 | 59 | gtk-ok 60 | True 61 | True 62 | True 63 | True 64 | 65 | 66 | True 67 | True 68 | 1 69 | 70 | 71 | 72 | 73 | False 74 | False 75 | 0 76 | 77 | 78 | 79 | 80 | True 81 | False 82 | <big>Select which users should have Piserver access</big> 83 | 84 | Users must be located under base DN: 85 | True 86 | 0 87 | 88 | 89 | False 90 | True 91 | 1 92 | 93 | 94 | 95 | 96 | True 97 | True 98 | True 99 | in 100 | False 101 | 102 | 103 | True 104 | True 105 | dnstore 106 | False 107 | 108 | 109 | 110 | 111 | 112 | dn 113 | 1 114 | 115 | 116 | 117 | 0 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | False 127 | True 128 | 2 129 | 130 | 131 | 132 | 133 | True 134 | False 135 | Users must be member of group: 136 | 0 137 | 138 | 139 | False 140 | True 141 | 3 142 | 143 | 144 | 145 | 146 | True 147 | True 148 | True 149 | in 150 | False 151 | 152 | 153 | True 154 | True 155 | groupstore 156 | False 157 | 158 | 159 | 160 | 161 | 162 | group 163 | 164 | 165 | 166 | 0 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | False 176 | True 177 | 4 178 | 179 | 180 | 181 | 182 | 183 | button1 184 | okbutton 185 | 186 | 187 | 188 | -------------------------------------------------------------------------------- /data/org.raspberrypi.piserver.policy: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Run piserver as root 6 | Authentication is required to run piserver as root 7 | folder-remote 8 | 9 | auth_admin 10 | auth_admin 11 | auth_admin 12 | 13 | /usr/bin/piserver 14 | true 15 | 16 | -------------------------------------------------------------------------------- /data/piserver.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Type=Application 3 | Version=1.0 4 | Name=PiServer 5 | Comment=Server for diskless Pi 3 6 | Icon=folder-remote 7 | Exec=sh -c "sudo piserver || pkexec piserver" 8 | Categories=Settings 9 | StartupNotify=false 10 | -------------------------------------------------------------------------------- /data/piserver.desktop.in: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Type=Application 3 | Version=1.0 4 | _Name=PiServer 5 | _Comment=Server for diskless Pi 3 6 | Icon=folder-remote 7 | Exec=sh -c "sudo piserver || pkexec piserver" 8 | Categories=Settings 9 | StartupNotify=false 10 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | piserver (0.7.6) bullseye; urgency=medium 2 | 3 | * Add support for newer Pi with MAC-addresses that start with d8:3a:dd 4 | 5 | -- Floris Bos Tue, 16 May 2023 22:04:36 +0200 6 | 7 | piserver (0.7.5) buster; urgency=medium 8 | 9 | * Fix "Cannot use erase() with null" error when using 10 | non-ad external LDAP server 11 | 12 | -- Floris Bos Sat, 10 Sep 2022 23:42:33 +0200 13 | 14 | piserver (0.7.4) buster; urgency=medium 15 | 16 | * Update convert script for bullseye 17 | 18 | -- Floris Bos Tue, 23 Nov 2021 20:55:19 +0100 19 | 20 | piserver (0.7.3) buster; urgency=medium 21 | 22 | * Fix GDateTime reference counting 23 | 24 | -- Floris Bos Sat, 03 Jul 2021 01:09:49 +0200 25 | 26 | piserver (0.7.2) buster; urgency=medium 27 | 28 | * Drop file system capabilities while running chroot shell 29 | * Importing SD cards: copy start menu folder structure too 30 | 31 | -- Floris Bos Thu, 27 May 2021 18:33:33 +0200 32 | 33 | piserver (0.7.1) buster; urgency=medium 34 | 35 | * Add support for converting OS images from SD card 36 | 37 | -- Floris Bos Thu, 29 Apr 2021 23:37:25 +0200 38 | 39 | piserver (0.7) buster; urgency=medium 40 | 41 | * Add new MAC OUI 42 | * pxz -> xz 43 | * Mention Pi 4 is also supported in UI 44 | 45 | -- Floris Bos Tue, 27 Apr 2021 14:38:33 +0200 46 | 47 | piserver (0.6) buster; urgency=medium 48 | 49 | [ Floris Bos ] 50 | * Add missing return statement 51 | 52 | -- Serge Schneider Wed, 19 Feb 2020 17:36:57 +0000 53 | 54 | piserver (0.5) buster; urgency=medium 55 | 56 | * Add export/save button for OS images 57 | * AD: support having users outside CN=users 58 | * LOCALE_DIR -> LOCALEDIR 59 | * Implement minimal download resuming support 60 | * Add support for lastlogin tracking, description 61 | * Fix mouse cursor on startup 62 | * Do not continously poll for SD card that usually is not present 63 | * Remove PubkeyAuthentication=no from pam_mount.conf.xml 64 | * Link to libatomic if needed 65 | * Fix setting /etc/nslcd.conf when using external LDAP/AD server 66 | * External LDAP/AD server: allow restricting access by user group 67 | * Warn user if STP is enabled on switch 68 | * Buster: fix disabling console auto-login 69 | * Add hosts screen: add new OUI to detection routine 70 | * sdtweak,pollonce -> sdtweak,poll_once 71 | * Buster: fix systemd-timesync time synchronization 72 | * Let systemd create the sshd runtime directory 73 | * Remove improper references to gksu 74 | * Remove references to Pixel 75 | 76 | -- Serge Schneider Mon, 14 Oct 2019 10:40:59 +0100 77 | 78 | piserver (0.4) buster; urgency=medium 79 | 80 | * Add 'clone OS' button 81 | * Remove dependency on gksudo 82 | 83 | -- Serge Schneider Thu, 07 Feb 2019 10:26:54 +0000 84 | 85 | piserver (0.3) stretch; urgency=medium 86 | 87 | * dhcpcd: disable link detection 88 | * Fix copying timezone setting from Piserver computer 89 | * Mount shared folders nolock 90 | * Shell button: call x-terminal-emulator instead of lxterminal 91 | 92 | -- Serge Schneider Tue, 03 Jul 2018 09:52:24 +0100 93 | 94 | piserver (0.2) stretch; urgency=medium 95 | 96 | * Add DhcpClient. Remove dependency on odhcploc 97 | * Force reconfig of existing slapd 98 | * Add initial .csv import support 99 | * Workaround for warnings being printed (gtk empty string bug) 100 | * Force systemd reload on install 101 | * Update convert_chrooted_cmds for Stretch 102 | * Remove data files on apt-get remove --purge piserver 103 | * Add minimal support for external LDAP/AD servers 104 | * Workaround for error when running "dpkg-reconfigure slapd" twice 105 | * Defer home directory creation to first login 106 | * Set certificate date to 9999 107 | * Add simple shared folder support 108 | * Do not create /etc/dnsmasq.d/piserver if not configured 109 | 110 | -- Serge Schneider Tue, 20 Mar 2018 10:53:01 +0000 111 | 112 | piserver (0.1) jessie; urgency=medium 113 | 114 | * Initial release 115 | 116 | -- Floris Bos Fri, 14 Apr 2017 21:10:11 +0100 117 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 9 -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: piserver 2 | Section: admin 3 | Priority: optional 4 | Maintainer: Floris Bos 5 | Build-Depends: debhelper (>= 9), cmake, libldap2-dev, libgtkmm-3.0-dev, libarchive-dev, libcurl4-openssl-dev, libcap-dev, intltool 6 | Standards-Version: 3.9.7 7 | 8 | Package: piserver 9 | Architecture: any 10 | Depends: ${shlibs:Depends}, ${misc:Depends}, binfmt-support [!armhf], qemu-user-static [!armhf] 11 | Recommends: policykit-1 12 | Description: Utility program to manage a network of diskless Pi 3 terminals 13 | Lets diskless Pi 3 terminals PXE boot from the network. 14 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: piserver 3 | Source: 4 | 5 | Files: * 6 | Copyright: 2017 Raspberry Pi 7 | License: TBD 8 | 9 | Files: json/json.hpp 10 | Copyright: 2013-2017 Niels Lohmann 11 | License: MIT 12 | __ _____ _____ _____ 13 | __| | __| | | | JSON for Modern C++ 14 | | | |__ | | | | | | version 2.1.1 15 | |_____|_____|_____|_|___| https://github.com/nlohmann/json 16 | . 17 | Licensed under the MIT License . 18 | Copyright (c) 2013-2017 Niels Lohmann . 19 | . 20 | Permission is hereby granted, free of charge, to any person obtaining a copy 21 | of this software and associated documentation files (the "Software"), to deal 22 | in the Software without restriction, including without limitation the rights 23 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 24 | copies of the Software, and to permit persons to whom the Software is 25 | furnished to do so, subject to the following conditions: 26 | . 27 | The above copyright notice and this permission notice shall be included in all 28 | copies or substantial portions of the Software. 29 | . 30 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 31 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 32 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 33 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 34 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 35 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 36 | SOFTWARE. 37 | 38 | Files: csv/csv.h 39 | Copyright: 2012-2015 Ben Strasser 40 | License: BSD-3 41 | Copyright: (2012-2015) Ben Strasser 42 | License: BSD-3 43 | . 44 | All rights reserved. 45 | . 46 | Redistribution and use in source and binary forms, with or without 47 | modification, are permitted provided that the following conditions are met: 48 | . 49 | 1. Redistributions of source code must retain the above copyright notice, 50 | this list of conditions and the following disclaimer. 51 | . 52 | 2. Redistributions in binary form must reproduce the above copyright notice, 53 | this list of conditions and the following disclaimer in the documentation 54 | and/or other materials provided with the distribution. 55 | . 56 | 3. Neither the name of the copyright holder nor the names of its contributors 57 | may be used to endorse or promote products derived from this software 58 | without specific prior written permission. 59 | . 60 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 61 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 62 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 63 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 64 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 65 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 66 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 67 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 68 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 69 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 70 | POSSIBILITY OF SUCH DAMAGE. 71 | -------------------------------------------------------------------------------- /debian/install: -------------------------------------------------------------------------------- 1 | debian/piserver_sshd_config /etc/ssh 2 | debian/piserver_ssh.service /lib/systemd/system 3 | -------------------------------------------------------------------------------- /debian/piserver_ssh.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=sftp-only SSH server used by piserver 3 | After=network.target auditd.service 4 | 5 | [Service] 6 | ExecStart=/usr/sbin/sshd -D -f /etc/ssh/piserver_sshd_config 7 | ExecReload=/bin/kill -HUP $MAINPID 8 | KillMode=process 9 | Restart=on-failure 10 | RuntimeDirectory=sshd 11 | RuntimeDirectoryMode=0755 12 | 13 | [Install] 14 | WantedBy=multi-user.target 15 | 16 | -------------------------------------------------------------------------------- /debian/piserver_sshd_config: -------------------------------------------------------------------------------- 1 | # Configuration file for Pi Server home directory serving 2 | 3 | Port 1022 4 | Protocol 2 5 | 6 | # Deny pi user 7 | DenyUsers pi 8 | 9 | # SFTP only 10 | 11 | ForceCommand internal-sftp 12 | AllowTcpForwarding no 13 | PermitTunnel no 14 | X11Forwarding no 15 | Subsystem sftp internal-sftp 16 | HostKey /etc/ssh/ssh_host_ed25519_key 17 | UsePrivilegeSeparation yes 18 | 19 | # Logging 20 | SyslogFacility AUTH 21 | LogLevel INFO 22 | 23 | # Authentication: 24 | LoginGraceTime 10 25 | PermitRootLogin no 26 | StrictModes yes 27 | 28 | RSAAuthentication no 29 | PubkeyAuthentication no 30 | 31 | # Don't read the user's ~/.rhosts and ~/.shosts files 32 | IgnoreRhosts yes 33 | # For this to work you will also need host keys in /etc/ssh_known_hosts 34 | RhostsRSAAuthentication no 35 | # similar for protocol version 2 36 | HostbasedAuthentication no 37 | 38 | # To enable empty passwords, change to yes (NOT RECOMMENDED) 39 | PermitEmptyPasswords no 40 | 41 | # Change to yes to enable challenge-response passwords (beware issues with 42 | # some PAM modules and threads) 43 | #ChallengeResponseAuthentication no 44 | 45 | # Change to no to disable tunnelled clear text passwords 46 | #PasswordAuthentication yes 47 | 48 | KbdInteractiveAuthentication yes 49 | TCPKeepAlive yes 50 | UsePAM yes 51 | 52 | -------------------------------------------------------------------------------- /debian/postinst: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | case "$1" in 6 | configure) 7 | systemctl daemon-reload 8 | ;; 9 | esac 10 | 11 | #DEBHELPER# 12 | 13 | exit 0 14 | -------------------------------------------------------------------------------- /debian/postrm: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | if [ "$1" = "remove" ]; then 6 | if [ -f /var/lib/piserver/settings.json ]; then 7 | echo 8 | echo =========================================== 9 | echo NOTE: piserver installed extra packages -such as a NFS and DHCP server- 10 | echo during installation. To remove those run: 11 | echo 12 | echo sudo apt-get remove --purge dnsmasq openssh-server nfs-kernel-server slapd libnss-ldapd libpam-ldapd ldap-utils gnutls-bin ntp 13 | echo =========================================== 14 | echo 15 | fi 16 | fi 17 | 18 | if [ "$1" = "purge" ]; then 19 | rm -f /var/lib/piserver/settings.json /var/lib/piserver/installed_distros.json /var/lib/piserver/hosts.json /usr/share/pam-configs/mkhomedir-piserver 20 | rm -rf /var/lib/piserver/os /var/lib/piserver/tftproot 21 | rmdir /var/lib/piserver || true 22 | fi 23 | 24 | #DEBHELPER# 25 | 26 | exit 0 27 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | # See debhelper(7) (uncomment to enable) 3 | # output every command that modifies files on the build system. 4 | #export DH_VERBOSE = 1 5 | 6 | %: 7 | dh $@ 8 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (native) 2 | -------------------------------------------------------------------------------- /macros/BuildTargetScript.cmake: -------------------------------------------------------------------------------- 1 | # This file is used to be invoked at build time. It generates the needed 2 | # resource XML file. 3 | 4 | # Input variables that need to provided when invoking this script: 5 | # GXML_OUTPUT The output file path where to save the XML file. 6 | # GXML_COMPRESS_ALL Sets all COMPRESS flags in all resources in resource 7 | # list. 8 | # GXML_NO_COMPRESS_ALL Removes all COMPRESS flags in all resources in 9 | # resource list. 10 | # GXML_STRIPBLANKS_ALL Sets all STRIPBLANKS flags in all resources in 11 | # resource list. 12 | # GXML_NO_STRIPBLANKS_ALL Removes all STRIPBLANKS flags in all resources in 13 | # resource list. 14 | # GXML_TOPIXDATA_ALL Sets all TOPIXDATA flags i nall resources in resource 15 | # list. 16 | # GXML_NO_TOPIXDATA_ALL Removes all TOPIXDATA flags in all resources in 17 | # resource list. 18 | # GXML_PREFIX Overrides the resource prefix that is prepended to 19 | # each relative name in registered resources. 20 | # GXML_RESOURCES The list of resource files. Whether absolute or 21 | # relative path is equal. 22 | 23 | # Include the GENERATE_GXML() function. 24 | include(${CMAKE_CURRENT_LIST_DIR}/GenerateGXML.cmake) 25 | 26 | # Set flags to actual invocation flags. 27 | if(GXML_COMPRESS_ALL) 28 | set(GXML_COMPRESS_ALL COMPRESS_ALL) 29 | endif() 30 | if(GXML_NO_COMPRESS_ALL) 31 | set(GXML_NO_COMPRESS_ALL NO_COMPRESS_ALL) 32 | endif() 33 | if(GXML_STRIPBLANKS_ALL) 34 | set(GXML_STRIPBLANKS_ALL STRIPBLANKS_ALL) 35 | endif() 36 | if(GXML_NO_STRIPBLANKS_ALL) 37 | set(GXML_NO_STRIPBLANKS_ALL NO_STRIPBLANKS_ALL) 38 | endif() 39 | if(GXML_TOPIXDATA_ALL) 40 | set(GXML_TOPIXDATA_ALL TOPIXDATA_ALL) 41 | endif() 42 | if(GXML_NO_TOPIXDATA_ALL) 43 | set(GXML_NO_TOPIXDATA_ALL NO_TOPIXDATA_ALL) 44 | endif() 45 | 46 | # Replace " " with ";" to import the list over the command line. Otherwise 47 | # CMake would interprete the passed resources as a whole string. 48 | string(REPLACE " " ";" GXML_RESOURCES ${GXML_RESOURCES}) 49 | 50 | # Invoke the gresource XML generation function. 51 | generate_gxml(${GXML_OUTPUT} 52 | ${GXML_COMPRESS_ALL} ${GXML_NO_COMPRESS_ALL} 53 | ${GXML_STRIPBLANKS_ALL} ${GXML_NO_STRIPBLANKS_ALL} 54 | ${GXML_TOPIXDATA_ALL} ${GXML_NO_TOPIXDATA_ALL} 55 | PREFIX ${GXML_PREFIX} 56 | RESOURCES ${GXML_RESOURCES}) 57 | 58 | -------------------------------------------------------------------------------- /macros/GenerateGXML.cmake: -------------------------------------------------------------------------------- 1 | include(CMakeParseArguments) 2 | 3 | # Generates the resource XML controlling file from resource list (and saves it 4 | # to xml_path). It's not recommended to use this function directly, since it 5 | # doesn't handle invalid arguments. It is used by the function 6 | # COMPILE_GRESOURCES() to create a custom command, so that this function is 7 | # invoked at build-time in script mode from CMake. 8 | function(GENERATE_GXML xml_path) 9 | # Available options: 10 | # COMPRESS_ALL, NO_COMPRESS_ALL Overrides the COMPRESS flag in all 11 | # registered resources. 12 | # STRIPBLANKS_ALL, NO_STRIPBLANKS_ALL Overrides the STRIPBLANKS flag in all 13 | # registered resources. 14 | # TOPIXDATA_ALL, NO_TOPIXDATA_ALL Overrides the TOPIXDATA flag in all 15 | # registered resources. 16 | set(GXML_OPTIONS COMPRESS_ALL NO_COMPRESS_ALL 17 | STRIPBLANKS_ALL NO_STRIPBLANKS_ALL 18 | TOPIXDATA_ALL NO_TOPIXDATA_ALL) 19 | 20 | # Available one value options: 21 | # PREFIX Overrides the resource prefix that is prepended to each 22 | # relative file name in registered resources. 23 | set(GXML_ONEVALUEARGS PREFIX) 24 | 25 | # Available multi-value options: 26 | # RESOURCES The list of resource files. Whether absolute or relative path is 27 | # equal, absolute paths are stripped down to relative ones. If the 28 | # absolute path is not inside the given base directory SOURCE_DIR 29 | # or CMAKE_CURRENT_SOURCE_DIR (if SOURCE_DIR is not overriden), 30 | # this function aborts. 31 | set(GXML_MULTIVALUEARGS RESOURCES) 32 | 33 | # Parse the arguments. 34 | cmake_parse_arguments(GXML_ARG 35 | "${GXML_OPTIONS}" 36 | "${GXML_ONEVALUEARGS}" 37 | "${GXML_MULTIVALUEARGS}" 38 | "${ARGN}") 39 | 40 | # Variable to store the double-quote (") string. Since escaping 41 | # double-quotes in strings is not possible we need a helper variable that 42 | # does this job for us. 43 | set(Q \") 44 | 45 | # Process resources and generate XML file. 46 | # Begin with the XML header and header nodes. 47 | set(GXML_XML_FILE "") 48 | set(GXML_XML_FILE "${GXML_XML_FILE}") 59 | 60 | # Process each resource. 61 | foreach(res ${GXML_ARG_RESOURCES}) 62 | if ("${res}" STREQUAL "COMPRESS") 63 | set(GXML_COMPRESSION_FLAG ON) 64 | elseif ("${res}" STREQUAL "STRIPBLANKS") 65 | set(GXML_STRIPBLANKS_FLAG ON) 66 | elseif ("${res}" STREQUAL "TOPIXDATA") 67 | set(GXML_TOPIXDATA_FLAG ON) 68 | else() 69 | # The file name. 70 | set(GXML_RESOURCE_PATH "${res}") 71 | 72 | # Append to real resource file dependency list. 73 | list(APPEND GXML_RESOURCES_DEPENDENCIES ${GXML_RESOURCE_PATH}) 74 | 75 | # Assemble node. 76 | set(GXML_RES_LINE "${GXML_RESOURCE_PATH}") 104 | 105 | # Append to file string. 106 | set(GXML_XML_FILE "${GXML_XML_FILE}${GXML_RES_LINE}") 107 | 108 | # Unset variables. 109 | unset(GXML_COMPRESSION_FLAG) 110 | unset(GXML_STRIPBLANKS_FLAG) 111 | unset(GXML_TOPIXDATA_FLAG) 112 | endif() 113 | 114 | endforeach() 115 | 116 | # Append closing nodes. 117 | set(GXML_XML_FILE "${GXML_XML_FILE}") 118 | 119 | # Use "file" function to generate XML controlling file. 120 | get_filename_component(xml_path_only_name "${xml_path}" NAME) 121 | file(WRITE ${xml_path} ${GXML_XML_FILE}) 122 | 123 | endfunction() 124 | 125 | -------------------------------------------------------------------------------- /macros/GlibCompileResourcesSupport.cmake: -------------------------------------------------------------------------------- 1 | # Path to this file. 2 | set(GCR_CMAKE_MACRO_DIR ${CMAKE_CURRENT_LIST_DIR}) 3 | 4 | # Finds the glib-compile-resources executable. 5 | find_program(GLIB_COMPILE_RESOURCES_EXECUTABLE glib-compile-resources) 6 | mark_as_advanced(GLIB_COMPILE_RESOURCES_EXECUTABLE) 7 | 8 | # Include the cmake files containing the functions. 9 | include(${GCR_CMAKE_MACRO_DIR}/CompileGResources.cmake) 10 | include(${GCR_CMAKE_MACRO_DIR}/GenerateGXML.cmake) 11 | 12 | -------------------------------------------------------------------------------- /po/LINGUAS: -------------------------------------------------------------------------------- 1 | en_GB 2 | -------------------------------------------------------------------------------- /po/POTFILES.in: -------------------------------------------------------------------------------- 1 | src/abstractadddistro.cpp 2 | src/abstractaddhost.cpp 3 | src/abstractadduser.cpp 4 | src/adddistrodialog.cpp 5 | src/addhostdialog.cpp 6 | src/adduserdialog.cpp 7 | src/dependenciesinstallthread.cpp 8 | src/dhcpanalyzer.cpp 9 | src/distribution.cpp 10 | src/downloadextractthread.cpp 11 | src/downloadthread.cpp 12 | src/edithostdialog.cpp 13 | src/edituserdialog.cpp 14 | src/host.cpp 15 | src/installwizard.cpp 16 | src/main.cpp 17 | src/mainwindow.cpp 18 | src/piserver.cpp 19 | data/adddistrodialog.glade 20 | data/edithostdialog.glade 21 | data/addhostdialog.glade 22 | data/edituserdialog.glade 23 | data/piserver.glade 24 | data/adduserdialog.glade 25 | data/firstuse.glade 26 | data/piserver.desktop.in 27 | -------------------------------------------------------------------------------- /scripts/50-piserver-dhcpcd-hook: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | PATH=/usr/bin:/usr/local/bin 4 | 5 | if ([ $reason = "RENEW" ] && [ $old_ip_address != $new_ip_address ]) ; then 6 | piserver --update-ip 7 | elif ([ $reason = "REBOOT" ] || [ $reason = "BOUND" ] || [ $reason = "STATIC" ]) ; then 8 | piserver --update-ip 9 | fi 10 | -------------------------------------------------------------------------------- /scripts/chroot_image.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set +e 4 | trap 'KILL -HUP ($jobs -p)' HUP 5 | 6 | if [ $# -lt 1 ]; then 7 | echo $0: script to chroot to PiServer OS folder 8 | echo Usage: sudo $0 \ [command to execute] 9 | exit 1 10 | fi 11 | 12 | ARCH=$(dpkg --print-architecture) 13 | OSFOLDER=$1 14 | PROG=$2 15 | [ -z "$PROG" ] && PROG="/bin/bash" 16 | 17 | if [ "$ARCH" != "armhf" ]; then 18 | command -v qemu-arm-static > /dev/null || { echo "qemu-arm-static command not found. Try: sudo apt-get install binfmt-support qemu-user-static"; exit 1; } 19 | fi 20 | 21 | if [ "$EUID" -ne 0 ]; then 22 | echo Script needs to be run as root 23 | exit 1 24 | fi 25 | 26 | if [ -n "$XAUTHORITY" ]; then 27 | cp "$XAUTHORITY" $OSFOLDER/root/Xauthority 28 | export XAUTHORITY=/root/Xauthority 29 | fi 30 | 31 | mv $OSFOLDER/etc/ld.so.preload $OSFOLDER/etc/ld.so.preload.disabled 32 | mount -t proc none $OSFOLDER/proc 33 | mount -t devpts devpts $OSFOLDER/dev/pts 34 | 35 | if [ "$ARCH" == "armhf" ]; then 36 | chroot $OSFOLDER $PROG 37 | else 38 | install -m 0755 /usr/bin/qemu-arm-static $OSFOLDER/usr/bin/qemu-arm-static 39 | chroot $OSFOLDER $PROG 40 | fi 41 | 42 | umount $OSFOLDER/dev/pts 43 | umount $OSFOLDER/proc 44 | 45 | mv $OSFOLDER/etc/ld.so.preload.disabled $OSFOLDER/etc/ld.so.preload 46 | 47 | if [ -n "$XAUTHORITY" ]; then 48 | rm -f "$OSFOLDER/root/Xauthority" 49 | fi 50 | -------------------------------------------------------------------------------- /scripts/convert.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Converts NOOBS style boot.tar.xz and root.tar.xz to image suitable for Piserver 4 | # 5 | 6 | set -e 7 | 8 | if [ $# -ne 3 ]; then 9 | echo "$0: script to convert boot.tar.xz/root.tar.xz to image suitable for PiServer" 10 | echo 11 | echo "Usage: $0 boot.tar.xz root.tar.xz output.tar.xz" 12 | exit 1 13 | fi 14 | 15 | BOOT_TARBALL="$1" 16 | ROOT_TARBALL="$2" 17 | OUTPUT_TARBALL="$3" 18 | ARCH=$(dpkg --print-architecture) 19 | CHROOT_SCRIPT=$(dirname "$0")/convert_chrooted_cmds 20 | 21 | if [ -d work ]; then 22 | echo "There already exists a temporary folder called 'work'" 23 | echo "Aborting." 24 | echo "If a previous session was aborted, delete the folder manually" 25 | exit 1 26 | fi 27 | 28 | if [ ! -f "$ROOT_TARBALL" ]; then 29 | echo "$ROOT_TARBALL does not exists" 30 | exit 1 31 | fi 32 | 33 | if [ ! -f "$BOOT_TARBALL" ]; then 34 | echo "$BOOT_TARBALL does not exists" 35 | exit 1 36 | fi 37 | 38 | if [ "$EUID" -ne 0 ]; then 39 | echo "Script needs to be run as root" 40 | exit 1 41 | fi 42 | 43 | if [[ "$ARCH" != arm* ]]; then 44 | command -v qemu-arm-static > /dev/null || { echo "qemu-arm-static command not found. Try: sudo apt-get install binfmt-support qemu-user-static"; exit 1; } 45 | fi 46 | command -v xz > /dev/null || { echo "xz command not found. Try: sudo apt install xz"; exit 1; } 47 | 48 | mkdir work 49 | echo "Extracting original files" 50 | tar xJf "$ROOT_TARBALL" -C work 51 | tar xJf "$BOOT_TARBALL" -C work/boot 52 | 53 | if ! chroot work true; then 54 | echo "Could not chroot into target environment. Please make sure binfmt is set up properly" 55 | echo "Deleting work directory" 56 | rm -rf work 57 | exit 1 58 | fi 59 | 60 | install -m 0755 "$CHROOT_SCRIPT" work/chrooted_cmds 61 | modprobe fuse 62 | 63 | echo "Installing extra software" 64 | 65 | mount -t proc none work/proc 66 | 67 | chroot work /chrooted_cmds 68 | 69 | umount work/proc 70 | 71 | rm -f work/chrooted_cmds 72 | rm -rf work/run/* 73 | 74 | echo "Compressing result" 75 | tar cf "$OUTPUT_TARBALL" -C work --use-compress-program "xz -T0" . 76 | echo "Deleting work directory" 77 | rm -rf work 78 | 79 | echo 80 | echo Complete! 81 | echo "Output is in $OUTPUT_TARBALL" 82 | echo 83 | -------------------------------------------------------------------------------- /scripts/convert_chrooted_cmds: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Used by convert.sh 4 | # Makes file system image suitable for read-only use, and install LDAP client software 5 | # 6 | 7 | cat >/etc/fstab <> /etc/fstab 37 | fi 38 | 39 | if [ -d /var/lib/lightdm ]; then 40 | echo "tmpfs /var/lib/lightdm tmpfs defaults,uid=lightdm,gid=lightdm,mode=755,nosuid 0 0" >> /etc/fstab 41 | fi 42 | 43 | debconf-set-selections < /etc/cups/client.conf 68 | fi 69 | 70 | cat >/etc/security/pam_mount.conf.xml < 72 | 73 | 74 | 75 | 77 | 78 | 79 | 80 | 81 | sshfs %(VOLUME) %(MNTPT) -o %(OPTIONS) 82 | 83 | EOF 84 | 85 | # Fix for forced password changes not working properly 86 | # https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=862225 87 | if grep -qv disable_interactive /etc/pam.d/common-password; then 88 | echo "password optional pam_mount.so disable_interactive" >> /etc/pam.d/common-password 89 | fi 90 | 91 | # Disable user rename 92 | cancel-rename pi 93 | userdel -r rpi-first-boot-wizard 94 | rm -f /etc/sudoers.d/010_wiz-nopasswd 95 | rm -f /etc/xdg/autostart/deluser.desktop 96 | 97 | if [ -f /etc/lightdm/lightdm.conf ]; then 98 | # Disable GUI auto-login 99 | sed -i "s/^autologin-user=.*/#autologin-user=/" /etc/lightdm/lightdm.conf 100 | # Store X authority in tmpfs instead of in home dir 101 | sed -i "s/#user-authority-in-system-dir=false/user-authority-in-system-dir=true/" /etc/lightdm/lightdm.conf 102 | # Hide user list 103 | sed -i "s/greeter-hide-users=false/greeter-hide-users=true/" /etc/lightdm/lightdm.conf 104 | fi 105 | 106 | # Disable console auto-login 107 | ln -fs /lib/systemd/system/getty@.service /etc/systemd/system/getty.target.wants/getty@tty1.service 108 | rm -f /etc/systemd/system/getty@tty1.service.d/autologin.conf 109 | 110 | # No point in saving random seed on read-only system 111 | rm -f "$DISTROROOT"/lib/systemd/system/sysinit.target.wants/systemd-random-seed.service 112 | 113 | # systemd-tmpfiles-setup should not try to change files on read-only file system 114 | rm -f /usr/lib/tmpfiles.d/dbus.conf /usr/lib/tmpfiles.d/debian.conf 115 | if [ -f /usr/lib/tmpfiles.d/legacy.conf ]; then 116 | sed -i 's@L /var/lock @#L /var/lock @g' /usr/lib/tmpfiles.d/legacy.conf 117 | fi 118 | if [ -d /var/lib/dbus ]; then 119 | ln -sf /etc/machine-id /var/lib/dbus/machine-id 120 | fi 121 | 122 | # Use our server as NTP server, as terminals may or may not be able to reach outside ones 123 | if [ -f /etc/ntp.conf ]; then 124 | sed -i 's/server /#server /g' /etc/ntp.conf 125 | echo "# Local network boot server" >> /etc/ntp.conf 126 | echo "server piserver iburst" >> /etc/ntp.conf 127 | elif [ -f /etc/systemd/timesyncd.conf ]; then 128 | echo "NTP=piserver" >> /etc/systemd/timesyncd.conf 129 | else 130 | echo "Warning: unable to find NTP configuration file. Configure it manually to use server 'piserver'" 131 | fi 132 | 133 | # dhcpcd: disable link detection as we do not want dhcpcd to tear down interface if cable gets disconnected temporarily 134 | if [ -f /etc/dhcpcd.conf ]; then 135 | echo "nolink" >> /etc/dhcpcd.conf 136 | { 137 | echo "# Piserver: disable wifi by default" 138 | echo "# If you have a need for wifi, remove both the following line, as well as nolink from /etc/dhcpcd.conf" 139 | echo "dtoverlay=disable-wifi" 140 | } >> /boot/config.txt 141 | fi 142 | 143 | # do not continously poll for SD card 144 | echo "dtparam=sd_poll_once" >> /boot/config.txt 145 | 146 | # 147 | # Remove non-relevant options from cmdline.txt 148 | # 149 | sed -i 's# init=\S\+##g' /boot/cmdline.txt 150 | sed -i 's# fsck.repair=yes##g' /boot/cmdline.txt 151 | sed -i 's# rootfstype=ext4##g' /boot/cmdline.txt 152 | 153 | # Remove dphys-swapfile and logrotate 154 | apt-get purge -y -q dphys-swapfile logrotate 155 | 156 | # Default to disabling per-user browser disk cache 157 | if [ -e /etc/chromium.d ]; then 158 | # shellcheck disable=SC2016 159 | echo 'export CHROMIUM_FLAGS="$CHROMIUM_FLAGS --disk-cache-dir=/dev/null --disk-cache-size=1"' >/etc/chromium.d/99-piserver-disable-cache 160 | fi 161 | if [ -e /etc/chromium-browser/customizations ]; then 162 | # shellcheck disable=SC2016 163 | echo 'CHROMIUM_FLAGS="$CHROMIUM_FLAGS --disk-cache-dir=/dev/null --disk-cache-size=1"' >/etc/chromium-browser/customizations/99-piserver-disable-cache 164 | fi 165 | 166 | # See if user made any customizations as Pi user and make them global 167 | if [ -e /home/pi/.local/share/applications ]; then 168 | echo "Making Pi user start menu modifications global" 169 | cp /home/pi/.local/share/applications/*.desktop /usr/share/applications || true 170 | fi 171 | 172 | if [ -e /home/pi/.local/share/desktop-directories ]; then 173 | echo "Making Pi user start menu folder modifications global" 174 | cp /home/pi/.local/share/desktop-directories/*.directory /usr/share/desktop-directories || true 175 | fi 176 | 177 | if [ -e /home/pi/.config/pcmanfm/LXDE-pi ]; then 178 | echo "Making Pi user desktop appearance setting changes global" 179 | cp /home/pi/.config/pcmanfm/LXDE-pi/*.conf /etc/xdg/pcmanfm/LXDE-pi || true 180 | fi 181 | 182 | # 183 | # User Pi has no place in multi-user system 184 | # 185 | deluser --remove-home pi 186 | delgroup pi 187 | 188 | # 189 | # Make all users part of some extra local groups when logged in through lightdm 190 | # 191 | sed -i '/@include common-auth/a auth optional pam_group.so' /etc/pam.d/lightdm 192 | { 193 | echo 194 | echo "# Added for Piserver" 195 | echo "lightdm;*;*;Al0000-2400;dialout, audio, video, spi, i2c, gpio, plugdev, input" 196 | } >> /etc/security/group.conf 197 | #echo "login;tty*;*;Al0000-2400;dialout, spi, i2c, gpio" >> /etc/security/group.conf 198 | -------------------------------------------------------------------------------- /scripts/convert_folder.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | if [ $# -ne 1 ]; then 6 | echo $0: script to make an operating system image suitable for PiServer 7 | echo 8 | echo Usage: $0 /directory/where/os/lives 9 | exit 1 10 | fi 11 | 12 | if [ ! -d "$1" ]; then 13 | echo $1 is not a directory 14 | exit 1 15 | fi 16 | 17 | ARCH=$(dpkg --print-architecture) 18 | CHROOT_SCRIPT=`dirname $0`/convert_chrooted_cmds 19 | 20 | if [ "$EUID" -ne 0 ]; then 21 | echo Script needs to be run as root 22 | exit 1 23 | fi 24 | 25 | if [ "$ARCH" != "armhf" ]; then 26 | command -v qemu-arm-static > /dev/null || { echo "qemu-arm-static command not found. Try: sudo apt-get install binfmt-support qemu-user-static"; exit 1; } 27 | fi 28 | 29 | install -m 0755 "$CHROOT_SCRIPT" $1/chrooted_cmds 30 | 31 | if [ -f "$1/etc/ld.so.preload" ]; then 32 | mv "$1/etc/ld.so.preload" "$1/etc/ld.so.preload.disabled" 33 | fi 34 | 35 | modprobe fuse 36 | 37 | echo Installing extra software 38 | 39 | mount -t proc none "$1/proc" 40 | 41 | if [ "$ARCH" == "armhf" ]; then 42 | chroot "$1" /bin/bash /chrooted_cmds 43 | else 44 | install -m 0755 /usr/bin/qemu-arm-static "$1/usr/bin/qemu-arm-static" 45 | chroot "$1" qemu-arm-static /bin/bash /chrooted_cmds 46 | fi 47 | 48 | umount "$1/proc" 49 | 50 | rm -f "$1/chrooted_cmds" 51 | rm -f "$1/usr/bin/qemu-arm-static" 52 | 53 | if [ -f "$1/etc/ld.so.preload.disabled" ]; then 54 | mv "$1/etc/ld.so.preload.disabled" "$1/etc/ld.so.preload" 55 | fi 56 | 57 | rm -rf "$1"/run/* 58 | -------------------------------------------------------------------------------- /scripts/mkhomedir-piserver: -------------------------------------------------------------------------------- 1 | Name: Create home directory during login 2 | Default: yes 3 | Priority: 900 4 | Session-Type: Additional 5 | Session: 6 | required pam_mkhomedir.so umask=0077 7 | -------------------------------------------------------------------------------- /scripts/postinstall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | # 6 | # Copy certain settings such as keyboard layout and timezone from the host system 7 | # 8 | 9 | if [ -d "$DISTROROOT/etc" ]; then 10 | cp -a /etc/timezone "$DISTROROOT/etc" 11 | cp -a /etc/localtime "$DISTROROOT/etc" 12 | fi 13 | if [ -d "$DISTROROOT/etc/default" ]; then 14 | cp -a /etc/default/keyboard "$DISTROROOT/etc/default" 15 | fi 16 | if [ -d "$DISTROROOT/etc/console-setup" ]; then 17 | cp -a /etc/console-setup/cached* "$DISTROROOT/etc/console-setup" 18 | fi 19 | 20 | # 21 | # Copy LDAP SSL certificate, and enable verification 22 | # 23 | 24 | if [ -f "$DISTROROOT/etc/nslcd.conf" ]; then 25 | if [ -n "$EXTERNAL_LDAP_CONFIG" ]; then 26 | echo "$EXTERNAL_LDAP_CONFIG" >> "$DISTROROOT/etc/nslcd.conf" 27 | else 28 | if [ -d "$DISTROROOT/etc/ssl/certs" ] && [ -f "/etc/ssl/certs/piserver.pem" ]; then 29 | cp -a /etc/ssl/certs/piserver.pem "$DISTROROOT/etc/ssl/certs" 30 | sed -i "s/#ssl off/ssl start_tls/g" "$DISTROROOT/etc/nslcd.conf" 31 | sed -i "s/#tls_reqcert never/tls_reqcert demand/g" "$DISTROROOT/etc/nslcd.conf" 32 | sed -i "s#/etc/ssl/certs/ca-certificates.crt#/etc/ssl/certs/piserver.pem#g" "$DISTROROOT/etc/nslcd.conf" 33 | fi 34 | fi 35 | fi 36 | -------------------------------------------------------------------------------- /src/abstractadddistro.h: -------------------------------------------------------------------------------- 1 | #ifndef ABSTRACTADDDISTRO_H 2 | #define ABSTRACTADDDISTRO_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | class DownloadThread; 9 | class DownloadExtractThread; 10 | class PiServer; 11 | class Distribution; 12 | 13 | class AbstractAddDistro 14 | { 15 | protected: 16 | AbstractAddDistro(PiServer *ps); 17 | virtual ~AbstractAddDistro(); 18 | void setupAddDistroFields(Glib::RefPtr builder, const std::string &cachedDistroInfo = ""); 19 | 20 | Gtk::RadioButton *_repoRadio, *_localfileRadio, *_urlRadio, *_sdRadio; 21 | Gtk::TreeView *_distroTree; 22 | Gtk::FileChooserButton *_fileChooser; 23 | Glib::RefPtr _repoStore, _sdStore; 24 | Gtk::Entry *_urlEntry, *_osnameEntry; 25 | Gtk::ProgressBar *_progressBar; 26 | Gtk::Label *_progressLabel1, *_progressLabel2, *_progressLabel3; 27 | Gtk::Grid *_sdGrid; 28 | Gtk::ComboBox *_sdCombo; 29 | Distribution *_dist; 30 | 31 | void startInstallation(); 32 | bool addDistroFieldsOk(); 33 | virtual void setAddDistroOkButton() = 0; 34 | virtual void onInstallationSuccess() = 0; 35 | virtual void onInstallationFailed(const std::string &error) = 0; 36 | 37 | private: 38 | PiServer *_ps; 39 | DownloadThread *_dt; 40 | DownloadExtractThread *_det; 41 | std::chrono::steady_clock::time_point _t1; 42 | sigc::connection _timer, _sdPollTimer; 43 | std::set _sdDrives; 44 | 45 | void _setSensitive(); 46 | void _parseDistroInfo(const std::string &data); 47 | std::string _getDistroname(const std::string &url); 48 | void _onRepolistDownloadComplete(); 49 | void _onRepolistDownloadFailed(); 50 | void _onInstallationSuccess(); 51 | void _onInstallationFailed(); 52 | bool _updateProgress(); 53 | bool _pollSDdrives(); 54 | }; 55 | 56 | #endif // ABSTRACTADDDISTRO_H 57 | -------------------------------------------------------------------------------- /src/abstractaddhost.cpp: -------------------------------------------------------------------------------- 1 | #include "abstractaddhost.h" 2 | #include "distribution.h" 3 | 4 | using namespace std; 5 | 6 | AbstractAddHost::AbstractAddHost(PiServer *ps) 7 | : _ps(ps) 8 | { 9 | } 10 | 11 | AbstractAddHost::~AbstractAddHost() 12 | { 13 | } 14 | 15 | void AbstractAddHost::setupAddHostFields(Glib::RefPtr builder) 16 | { 17 | builder->get_widget("hosttree", _hosttree); 18 | _hoststore = Glib::RefPtr::cast_dynamic( builder->get_object("hoststore") ); 19 | _hoststore->clear(); 20 | 21 | _watcher.signal_macDetected().connect( sigc::mem_fun(this, &AbstractAddHost::on_macDetected) ); 22 | dynamic_cast(_hosttree->get_column_cell_renderer(0))->signal_toggled().connect( 23 | sigc::mem_fun(this, &AbstractAddHost::on_hostToggled) ); 24 | _stpdetect.signal_detected().connect( sigc::mem_fun(this, &AbstractAddHost::on_stpDetected) ); 25 | 26 | try 27 | { 28 | _watcher.startListening(); 29 | } 30 | catch (std::exception) 31 | { 32 | Gtk::MessageDialog d(_("Error listening for DHCP traffic.\n" 33 | "Uninstall any other DHCP servers running on this computer.")); 34 | d.run(); 35 | } 36 | 37 | try 38 | { 39 | std::string ifname = _ps->getSetting("interface"); 40 | if (ifname.empty()) 41 | { 42 | map ifaces = _ps->getInterfaces(); 43 | for (auto kv : ifaces) 44 | { 45 | if (kv.first != "lo") 46 | { 47 | ifname = kv.first; 48 | break; 49 | } 50 | } 51 | } 52 | 53 | if (!ifname.empty()) 54 | { 55 | _stpdetect.startListening(ifname); 56 | } 57 | } 58 | catch (const std::exception &e) 59 | { 60 | Gtk::MessageDialog d( e.what() ); 61 | } 62 | } 63 | 64 | void AbstractAddHost::on_macDetected(std::string mac) 65 | { 66 | if (!_ps->getHosts()->count(mac)) 67 | { 68 | auto row = _hoststore->append(); 69 | row->set_value(0, true); 70 | row->set_value(1, mac); 71 | setAddHostOkButton(); 72 | } 73 | } 74 | 75 | bool AbstractAddHost::addHostFieldsOk() 76 | { 77 | int selected = 0; 78 | 79 | for (auto &row : _hoststore->children() ) 80 | { 81 | bool checked; 82 | row->get_value(0, checked); 83 | if (checked) 84 | selected++; 85 | } 86 | 87 | return selected > 0; 88 | } 89 | 90 | void AbstractAddHost::on_hostToggled(const Glib::ustring& path) 91 | { 92 | auto iter = _hoststore->get_iter(path); 93 | bool checked; 94 | iter->get_value(0, checked); 95 | iter->set_value(0, !checked); 96 | setAddHostOkButton(); 97 | } 98 | 99 | void AbstractAddHost::addHosts(Distribution *distro, const string &description) 100 | { 101 | set macs; 102 | 103 | for (auto &row : _hoststore->children() ) 104 | { 105 | bool checked; 106 | string mac; 107 | 108 | row->get_value(0, checked); 109 | if (checked) 110 | { 111 | row->get_value(1, mac); 112 | macs.insert(mac); 113 | } 114 | } 115 | 116 | _ps->addHosts(macs, distro, description); 117 | } 118 | 119 | void AbstractAddHost::on_stpDetected(std::string info) 120 | { 121 | Gtk::MessageDialog d(Glib::ustring::compose(_( 122 | "STP seems to be enabled on your switch, which\n" 123 | "can conflict with network booting.\n" 124 | "Ask your network administrator to either disable it completely\n" 125 | "on ports connecting Pi, or change it to the portfast/rapid variant.\n\n" 126 | "(%1)"), info)); 127 | d.run(); 128 | } 129 | -------------------------------------------------------------------------------- /src/abstractaddhost.h: -------------------------------------------------------------------------------- 1 | #ifndef ABSTRACTADDHOST_H 2 | #define ABSTRACTADDHOST_H 3 | 4 | #include 5 | #include "dhcpanalyzer.h" 6 | #include "stpanalyzer.h" 7 | #include "piserver.h" 8 | 9 | class AbstractAddHost : public sigc::trackable 10 | { 11 | protected: 12 | AbstractAddHost(PiServer *ps); 13 | virtual ~AbstractAddHost(); 14 | void setupAddHostFields(Glib::RefPtr builder); 15 | bool addHostFieldsOk(); 16 | void addHosts(Distribution *distro, const std::string &description = ""); 17 | virtual void setAddHostOkButton() = 0; 18 | 19 | /* Slots */ 20 | void on_macDetected(std::string mac); 21 | void on_hostToggled(const Glib::ustring& path); 22 | void on_stpDetected(std::string info); 23 | 24 | Gtk::TreeView *_hosttree; 25 | Glib::RefPtr _hoststore; 26 | DhcpAnalyzer _watcher; 27 | StpAnalyzer _stpdetect; 28 | 29 | private: 30 | PiServer *_ps; 31 | }; 32 | 33 | #endif // ABSTRACTADDHOST_H 34 | -------------------------------------------------------------------------------- /src/abstractadduser.cpp: -------------------------------------------------------------------------------- 1 | #include "abstractadduser.h" 2 | #include "piserver.h" 3 | #include 4 | 5 | using namespace std; 6 | 7 | AbstractAddUser::AbstractAddUser(PiServer *ps) 8 | : _ps(ps) 9 | { 10 | } 11 | 12 | AbstractAddUser::~AbstractAddUser() 13 | { 14 | } 15 | 16 | void AbstractAddUser::setupAddUserFields(Glib::RefPtr builder) 17 | { 18 | builder->get_widget("showpasscheck", _showPassCheck); 19 | builder->get_widget("forcepasschangecheck", _forcePassChangeCheck); 20 | _showPassCheck->signal_toggled().connect(sigc::mem_fun(this, &AbstractAddUser::on_showPassToggled)); 21 | 22 | for (int i=1; i<=5; i++) 23 | { 24 | Gtk::Entry *e; 25 | builder->get_widget("user"+to_string(i), e); 26 | _userEntry.push_back(e); 27 | e->signal_changed().connect(sigc::mem_fun(this, &AbstractAddUser::setAddUserOkButton)); 28 | builder->get_widget("pass"+to_string(i), e); 29 | _passEntry.push_back(e); 30 | e->signal_changed().connect(sigc::mem_fun(this, &AbstractAddUser::setAddUserOkButton)); 31 | } 32 | } 33 | 34 | bool AbstractAddUser::addUserFieldsOk() 35 | { 36 | bool ok = false; 37 | 38 | /* Enable 'ok' button if at least one username & password combo is filled in. 39 | and there aren't any entries that lack a counterpart */ 40 | for (int i=0; i < _userEntry.size(); i++) 41 | { 42 | bool userSet = _userEntry[i]->get_text_length() > 0; 43 | bool passSet = _passEntry[i]->get_text_length() > 0; 44 | 45 | if (userSet && passSet) 46 | ok = true; 47 | else if (userSet && !passSet) 48 | { 49 | ok = false; 50 | break; 51 | } 52 | else if (!userSet && passSet) 53 | { 54 | ok = false; 55 | break; 56 | } 57 | } 58 | 59 | return ok; 60 | } 61 | 62 | void AbstractAddUser::on_showPassToggled() 63 | { 64 | bool showPass = _showPassCheck->get_active(); 65 | 66 | for (auto e : _passEntry) 67 | { 68 | e->set_visibility(showPass); 69 | } 70 | } 71 | 72 | bool AbstractAddUser::checkUserAvailability(bool useLDAP) 73 | { 74 | set notwellformed, alreadyexists; 75 | 76 | for (int i=0; i < _userEntry.size(); i++) 77 | { 78 | string user = _userEntry[i]->get_text(); 79 | 80 | if (!user.length()) 81 | continue; 82 | 83 | try 84 | { 85 | if (!_ps->isUsernameValid(user)) 86 | notwellformed.insert(user); 87 | else if (!_ps->isUsernameAvailableLocally(user) 88 | || (useLDAP && !_ps->isUsernameAvailable(user))) 89 | alreadyexists.insert(user); 90 | } 91 | catch (exception &e) 92 | { 93 | /* Can error out checking availibility if it is unable to connect to LDAP */ 94 | Gtk::MessageDialog d(e.what()); 95 | d.run(); 96 | return false; 97 | } 98 | } 99 | 100 | if (!notwellformed.empty() || !alreadyexists.empty()) 101 | { 102 | string errmsg; 103 | 104 | if (!notwellformed.empty()) 105 | { 106 | errmsg += _("The following user names contain invalid characters: "); 107 | for (auto user : notwellformed) 108 | errmsg += "'"+user+"' "; 109 | 110 | errmsg += _("\nOnly the following characters are allowed: a-z 0-9 . - _\n"); 111 | } 112 | if (!alreadyexists.empty()) 113 | { 114 | errmsg += _("The following user names already exist on the system or this computer: "); 115 | for (auto user : alreadyexists) 116 | errmsg += "'"+user+"' "; 117 | } 118 | 119 | Gtk::MessageDialog d(errmsg); 120 | d.run(); 121 | 122 | return false; 123 | } 124 | 125 | return true; 126 | } 127 | 128 | void AbstractAddUser::addUsers() 129 | { 130 | for (int i=0; i < _userEntry.size(); i++) 131 | { 132 | string user = _userEntry[i]->get_text(); 133 | string pass = _passEntry[i]->get_text(); 134 | 135 | if (!user.length()) 136 | continue; 137 | 138 | try 139 | { 140 | _ps->addUser(user, pass, _forcePassChangeCheck->get_active()); 141 | } 142 | catch (exception &e) 143 | { 144 | Gtk::MessageDialog d(e.what()); 145 | d.run(); 146 | } 147 | } 148 | 149 | // Flush nscd cache 150 | if ( ::system("nscd -i passwd") != 0 ) { } 151 | } 152 | -------------------------------------------------------------------------------- /src/abstractadduser.h: -------------------------------------------------------------------------------- 1 | #ifndef ABSTRACTADDUSER_H 2 | #define ABSTRACTADDUSER_H 3 | 4 | #include 5 | 6 | class PiServer; 7 | 8 | class AbstractAddUser 9 | { 10 | protected: 11 | AbstractAddUser(PiServer *ps); 12 | void setupAddUserFields(Glib::RefPtr builder); 13 | virtual ~AbstractAddUser(); 14 | virtual void setAddUserOkButton() = 0; 15 | bool addUserFieldsOk(); 16 | void on_showPassToggled(); 17 | bool checkUserAvailability(bool useLDAP); 18 | void addUsers(); 19 | 20 | Gtk::CheckButton *_forcePassChangeCheck, *_showPassCheck; 21 | std::vector _userEntry; 22 | std::vector _passEntry; 23 | 24 | private: 25 | PiServer *_ps; 26 | }; 27 | 28 | #endif // ABSTRACTADDUSER_H 29 | -------------------------------------------------------------------------------- /src/activediscovery.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Class to auto-detect Active Directory servers 3 | */ 4 | 5 | #include "activediscovery.h" 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | using namespace std; 13 | 14 | ActiveDiscovery::ActiveDiscovery() 15 | : _timeoutInSeconds(3) 16 | { 17 | _dhcp.signal_dhcpOfferDomain().connect( sigc::mem_fun(this, &ActiveDiscovery::onDhcpOfferWithDomain) ); 18 | } 19 | 20 | ActiveDiscovery::~ActiveDiscovery() 21 | { 22 | if (_timer.connected()) 23 | { 24 | _timer.disconnect(); 25 | } 26 | } 27 | 28 | void ActiveDiscovery::startDiscovery(const std::string &networkInterface) 29 | { 30 | /* Step 1: try to obtain domain name from DHCP server */ 31 | _domain.clear(); 32 | _dhcp.sendDhcpDiscover(networkInterface); 33 | _timer = Glib::signal_timeout().connect_seconds( sigc::mem_fun(this, &ActiveDiscovery::on_timeout), _timeoutInSeconds); 34 | } 35 | 36 | /* Received a DHCPOFFER that includes our domain name */ 37 | void ActiveDiscovery::onDhcpOfferWithDomain(const std::string &domain) 38 | { 39 | if (!_domain.empty()) /* Only handle first OFFER received */ 40 | return; 41 | 42 | /* Step 2: try to obtain AD server address from DNS SRV records */ 43 | std::string query = "_ldap._tcp."+domain; 44 | struct __res_state state = { 0 }; 45 | ns_msg handle; 46 | unsigned char buf[1024]; 47 | int len; 48 | _domain = domain; 49 | 50 | res_ninit(&state); 51 | len = res_nquery(&state, query.c_str(), C_IN, ns_t_srv, buf, sizeof(buf)); 52 | if (len < 0 || ns_initparse(buf, len, &handle) != 0) 53 | { 54 | _discoveryFailed(); 55 | return; 56 | } 57 | 58 | int rrcount = ns_msg_count(handle, ns_s_an); 59 | ns_rr rr; 60 | 61 | for (int i=0; i 0) 67 | { 68 | string server = fqdn; 69 | 70 | if (server.find(".local") != string::npos) 71 | { 72 | /* If hostname ends with .local use IP from DNS reply (additional section), instead of host name. 73 | Some have .local configured as domain on their Windows DNS server, while Linux expects 74 | .local domains to be mDNS (avahi), not regular DNS */ 75 | if (ns_parserr(&handle, ns_s_ar, i, &rr) == 0 && ns_rr_type(rr) == ns_t_a) 76 | { 77 | char ipstr[INET_ADDRSTRLEN] = {0}; 78 | inet_ntop(AF_INET, ns_rr_rdata(rr), ipstr, INET_ADDRSTRLEN); 79 | server = ipstr; 80 | } 81 | } 82 | 83 | if (_timer.connected()) 84 | { 85 | _timer.disconnect(); 86 | } 87 | _signal_discovered.emit(server, _domain); 88 | return; 89 | } 90 | } 91 | } 92 | 93 | _discoveryFailed(); 94 | } 95 | 96 | void ActiveDiscovery::_discoveryFailed() 97 | { 98 | if (_timer.connected()) 99 | { 100 | _timer.disconnect(); 101 | } 102 | 103 | _signal_discoveryFailed.emit(); 104 | } 105 | 106 | bool ActiveDiscovery::on_timeout() 107 | { 108 | _signal_discoveryFailed.emit(); 109 | return false; 110 | } 111 | -------------------------------------------------------------------------------- /src/activediscovery.h: -------------------------------------------------------------------------------- 1 | #ifndef ACTIVEDISCOVERY_H 2 | #define ACTIVEDISCOVERY_H 3 | 4 | /** 5 | * Class to auto-detect Active Directory servers 6 | */ 7 | 8 | #include 9 | #include 10 | #include "dhcpclient.h" 11 | 12 | class ActiveDiscovery 13 | { 14 | public: 15 | ActiveDiscovery(); 16 | virtual ~ActiveDiscovery(); 17 | void startDiscovery(const std::string &networkInterface); 18 | 19 | /* signals */ 20 | inline sigc::signal signal_discovered() 21 | { 22 | return _signal_discovered; 23 | } 24 | 25 | inline sigc::signal signal_discoveryFailed() 26 | { 27 | return _signal_discoveryFailed; 28 | } 29 | 30 | protected: 31 | int _timeoutInSeconds; 32 | std::string _domain; 33 | DhcpClient _dhcp; 34 | sigc::signal _signal_discovered; 35 | sigc::signal _signal_discoveryFailed; 36 | sigc::connection _timer; 37 | 38 | void _discoveryFailed(); 39 | 40 | /* slots */ 41 | void onDhcpOfferWithDomain(const std::string &domain); 42 | bool on_timeout(); 43 | }; 44 | 45 | #endif // ACTIVEDISCOVERY_H 46 | -------------------------------------------------------------------------------- /src/adddistrodialog.cpp: -------------------------------------------------------------------------------- 1 | #include "adddistrodialog.h" 2 | 3 | using namespace std; 4 | 5 | AddDistroDialog::AddDistroDialog(MainWindow *mw, PiServer *ps, const std::string &cachedDistroInfo, Gtk::Window *parent) 6 | : AbstractAddDistro(ps), _mw(mw) 7 | { 8 | auto builder = Gtk::Builder::create_from_resource("/data/adddistrodialog.glade"); 9 | builder->get_widget("assistant", _assistant); 10 | builder->get_widget("adddistropage", _addDistroPage); 11 | builder->get_widget("progresspage", _progressPage); 12 | setupAddDistroFields(builder, cachedDistroInfo); 13 | _assistant->signal_cancel().connect( sigc::mem_fun(this, &AddDistroDialog::onClose) ); 14 | _assistant->signal_close().connect( sigc::mem_fun(this, &AddDistroDialog::onClose) ); 15 | _assistant->signal_prepare().connect( sigc::mem_fun(this, &AddDistroDialog::onPagePrepare)); 16 | 17 | if (parent) 18 | _assistant->set_transient_for(*parent); 19 | } 20 | 21 | AddDistroDialog::~AddDistroDialog() 22 | { 23 | } 24 | 25 | void AddDistroDialog::show() 26 | { 27 | _assistant->show(); 28 | } 29 | 30 | void AddDistroDialog::selectDistro(const std::string &name) 31 | { 32 | for (auto &row : _repoStore->children() ) 33 | { 34 | string rn; 35 | row->get_value(1, rn); 36 | if (rn == name) 37 | { 38 | Gtk::TreePath path(row); 39 | _distroTree->set_cursor(path); 40 | break; 41 | } 42 | } 43 | } 44 | 45 | void AddDistroDialog::onClose() 46 | { 47 | _assistant->hide(); 48 | } 49 | 50 | void AddDistroDialog::onPagePrepare(Gtk::Widget *newPage) 51 | { 52 | if (newPage == _progressPage) 53 | { 54 | startInstallation(); 55 | } 56 | } 57 | 58 | void AddDistroDialog::setAddDistroOkButton() 59 | { 60 | _assistant->set_page_complete(*_addDistroPage, 61 | addDistroFieldsOk() ); 62 | } 63 | 64 | void AddDistroDialog::onInstallationSuccess() 65 | { 66 | _mw->_reloadDistro(); 67 | _assistant->set_page_complete(*_progressPage, true); 68 | _assistant->next_page(); 69 | } 70 | 71 | void AddDistroDialog::onInstallationFailed(const std::string &error) 72 | { 73 | Gtk::MessageDialog d(error); 74 | d.run(); 75 | _assistant->hide(); 76 | } 77 | -------------------------------------------------------------------------------- /src/adddistrodialog.h: -------------------------------------------------------------------------------- 1 | #ifndef ADDDISTRODIALOG_H 2 | #define ADDDISTRODIALOG_H 3 | 4 | #include "abstractadddistro.h" 5 | #include "mainwindow.h" 6 | 7 | class AddDistroDialog : public AbstractAddDistro 8 | { 9 | public: 10 | AddDistroDialog(MainWindow *mw, PiServer *ps, const std::string &cachedDistroInfo = "", Gtk::Window *parent = NULL); 11 | virtual ~AddDistroDialog(); 12 | void show(); 13 | void selectDistro(const std::string &name); 14 | 15 | protected: 16 | MainWindow *_mw; 17 | Gtk::Assistant *_assistant; 18 | Gtk::Widget *_addDistroPage, *_progressPage; 19 | 20 | void onClose(); 21 | void onPagePrepare(Gtk::Widget *newPage); 22 | void setAddDistroOkButton(); 23 | void onInstallationSuccess(); 24 | void onInstallationFailed(const std::string &error); 25 | }; 26 | 27 | #endif // ADDDISTRODIALOG_H 28 | -------------------------------------------------------------------------------- /src/addfolderdialog.cpp: -------------------------------------------------------------------------------- 1 | #include "addfolderdialog.h" 2 | #include "piserver.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | using namespace std; 11 | 12 | AddFolderDialog::AddFolderDialog(PiServer *ps, Gtk::Window *parent) 13 | : _ps(ps) 14 | { 15 | auto builder = Gtk::Builder::create_from_resource("/data/addfolderdialog.glade"); 16 | builder->get_widget("addfolderdialog", _dialog); 17 | builder->get_widget("okbutton", _okButton); 18 | builder->get_widget("nameentry", _nameEntry); 19 | builder->get_widget("ownerentry", _ownerEntry); 20 | const char *user = getenv("SUDO_USER"); 21 | if (!user) 22 | user = getenv("USER"); 23 | if (user) 24 | _ownerEntry->set_text(user); 25 | 26 | _nameEntry->signal_changed().connect(sigc::mem_fun(this, &AddFolderDialog::setOkSensitive)); 27 | _ownerEntry->signal_changed().connect(sigc::mem_fun(this, &AddFolderDialog::setOkSensitive)); 28 | if (parent) 29 | _dialog->set_transient_for(*parent); 30 | } 31 | 32 | AddFolderDialog::~AddFolderDialog() 33 | { 34 | delete _dialog; 35 | } 36 | 37 | bool AddFolderDialog::exec() 38 | { 39 | while (_dialog->run() == Gtk::RESPONSE_OK) 40 | { 41 | string name = _nameEntry->get_text(); 42 | string owner = _ownerEntry->get_text(); 43 | string path = PISERVER_SHAREDROOT "/" + name; 44 | struct passwd *userinfo; 45 | 46 | try 47 | { 48 | userinfo = ::getpwnam(owner.c_str()); 49 | if (!userinfo) 50 | throw runtime_error(_("Specified owner does not exist")); 51 | 52 | if (::mkdir(path.c_str(), 0755) == -1) 53 | throw runtime_error(Glib::ustring::compose(_("Error creating folder '%1': %2"), path, ::strerror(errno))); 54 | if (::chown(path.c_str(), userinfo->pw_uid, userinfo->pw_gid) == -1) 55 | throw runtime_error(Glib::ustring::compose(_("Error changing owner of folder '%1': %2"), path, ::strerror(errno))); 56 | 57 | return true; 58 | } 59 | catch (const runtime_error &r) 60 | { 61 | Gtk::MessageDialog d(r.what()); 62 | d.set_parent(*_dialog); 63 | d.run(); 64 | } 65 | } 66 | 67 | return false; 68 | } 69 | 70 | bool AddFolderDialog::isNameValid(const std::string &name) 71 | { 72 | if (name[0] == '-' || name[0] == '.' || name[0] == ' ') 73 | return false; 74 | 75 | for (char c : name) 76 | { 77 | if (!isalnum(c) && c != '.' && c != '_' && c != '-' && c != ' ') 78 | return false; 79 | } 80 | 81 | return true; 82 | } 83 | 84 | void AddFolderDialog::setOkSensitive() 85 | { 86 | bool ok = _nameEntry->get_text_length() 87 | && _ownerEntry->get_text_length() 88 | && isNameValid(_nameEntry->get_text() ); 89 | _okButton->set_sensitive(ok); 90 | } 91 | -------------------------------------------------------------------------------- /src/addfolderdialog.h: -------------------------------------------------------------------------------- 1 | #ifndef ADDFOLDERDIALOG_H 2 | #define ADDFOLDERDIALOG_H 3 | 4 | #include 5 | #include 6 | 7 | class PiServer; 8 | 9 | class AddFolderDialog 10 | { 11 | public: 12 | AddFolderDialog(PiServer *ps, Gtk::Window *parent); 13 | virtual ~AddFolderDialog(); 14 | bool exec(); 15 | 16 | protected: 17 | PiServer *_ps; 18 | Gtk::Dialog *_dialog; 19 | Gtk::Button *_okButton; 20 | Gtk::Entry *_nameEntry, *_ownerEntry; 21 | 22 | bool isNameValid(const std::string &name); 23 | void setOkSensitive(); 24 | 25 | }; 26 | 27 | #endif // ADDFOLDERDIALOG_H 28 | -------------------------------------------------------------------------------- /src/addhostdialog.cpp: -------------------------------------------------------------------------------- 1 | #include "addhostdialog.h" 2 | #include "distribution.h" 3 | #include "host.h" 4 | #include 5 | #include 6 | 7 | using namespace std; 8 | 9 | AddHostDialog::AddHostDialog(PiServer *ps, Gtk::Window *parent) 10 | : AbstractAddHost(ps), _ps(ps) 11 | { 12 | auto builder = Gtk::Builder::create_from_resource("/data/addhostdialog.glade"); 13 | setupAddHostFields(builder); 14 | builder->get_widget("addhostdialog", _dialog); 15 | builder->get_widget("okbutton", _okbutton); 16 | builder->get_widget("oscombo", _oscombo); 17 | builder->get_widget("descriptionentry", _descriptionentry); 18 | _distrostore = Glib::RefPtr::cast_dynamic( builder->get_object("distrostore") ); 19 | if (parent) 20 | _dialog->set_transient_for(*parent); 21 | 22 | _distrostore->clear(); 23 | auto distros = _ps->getDistributions(); 24 | 25 | for (auto &kv : *distros) 26 | { 27 | auto row = _distrostore->append(); 28 | row->set_value(0, kv.second->name()); 29 | } 30 | if (!distros->empty()) 31 | _oscombo->set_active(0); 32 | 33 | _descriptionentry->grab_focus(); 34 | } 35 | 36 | AddHostDialog::~AddHostDialog() 37 | { 38 | delete _dialog; 39 | } 40 | 41 | bool AddHostDialog::exec() 42 | { 43 | if (_dialog->run() == Gtk::RESPONSE_OK) 44 | { 45 | string distroname; 46 | string description = _descriptionentry->get_text(); 47 | _oscombo->get_active()->get_value(0, distroname); 48 | Distribution *distro = _ps->getDistributions()->at(distroname); 49 | 50 | addHosts(distro, description); 51 | 52 | return true; 53 | } 54 | 55 | return false; 56 | } 57 | 58 | void AddHostDialog::setAddHostOkButton() 59 | { 60 | _okbutton->set_sensitive( addHostFieldsOk() ); 61 | } 62 | -------------------------------------------------------------------------------- /src/addhostdialog.h: -------------------------------------------------------------------------------- 1 | #ifndef ADDHOSTWINDOW_H 2 | #define ADDHOSTWINDOW_H 3 | 4 | #include "piserver.h" 5 | #include "abstractaddhost.h" 6 | #include 7 | 8 | class AddHostDialog : private AbstractAddHost 9 | { 10 | public: 11 | AddHostDialog(PiServer *ps, Gtk::Window *parent = NULL); 12 | virtual ~AddHostDialog(); 13 | bool exec(); 14 | 15 | protected: 16 | PiServer *_ps; 17 | Gtk::Dialog *_dialog; 18 | Gtk::Button *_okbutton; 19 | Gtk::ComboBox *_oscombo; 20 | Gtk::Entry *_descriptionentry; 21 | Glib::RefPtr _distrostore; 22 | 23 | virtual void setAddHostOkButton(); 24 | }; 25 | 26 | #endif // ADDHOSTWINDOW_H 27 | -------------------------------------------------------------------------------- /src/adduserdialog.cpp: -------------------------------------------------------------------------------- 1 | #include "adduserdialog.h" 2 | #include "csv/csv.h" 3 | #include 4 | #include 5 | 6 | using namespace std; 7 | 8 | AddUserDialog::AddUserDialog(PiServer *ps, Gtk::Window *parent) 9 | : AbstractAddUser(ps), _ps(ps), _parentWindow(parent) 10 | { 11 | auto builder = Gtk::Builder::create_from_resource("/data/adduserdialog.glade"); 12 | setupAddUserFields(builder); 13 | builder->get_widget("adduserdialog", _dialog); 14 | builder->get_widget("okbutton", _okbutton); 15 | builder->get_widget("grid", _grid); 16 | if (parent) 17 | _dialog->set_transient_for(*parent); 18 | } 19 | 20 | AddUserDialog::~AddUserDialog() 21 | { 22 | delete _dialog; 23 | } 24 | 25 | bool AddUserDialog::exec() 26 | { 27 | while (_dialog->run() == Gtk::RESPONSE_OK) 28 | { 29 | if (checkUserAvailability(true)) 30 | { 31 | addUsers(); 32 | return true; 33 | } 34 | } 35 | 36 | return false; 37 | } 38 | 39 | void AddUserDialog::setAddUserOkButton() 40 | { 41 | _okbutton->set_sensitive( addUserFieldsOk() ); 42 | } 43 | 44 | /* Import CSV file and show result in normal adduser dialog, so user can check before adding */ 45 | bool AddUserDialog::importCSV(const string &filename) 46 | { 47 | try 48 | { 49 | io::CSVReader<2, io::trim_chars<' '>, io::double_quote_escape<',','\"'> > csv(filename); 50 | map users; 51 | set notwellformed, duplicates, alreadyexists; 52 | string user, password; 53 | 54 | csv.set_header("user", "password"); 55 | while (csv.read_row(user, password)) 56 | { 57 | /* Normalize user names to lower case */ 58 | std::transform(user.begin(), user.end(), user.begin(), ::tolower); 59 | /* Replace any spaces with dots */ 60 | std::replace(user.begin(), user.end(), ' ', '.'); 61 | 62 | if (users.count(user)) 63 | { 64 | duplicates.insert(user); 65 | } 66 | else 67 | { 68 | if (!_ps->isUsernameValid(user)) 69 | notwellformed.insert(user); 70 | else if (!_ps->isUsernameAvailableLocally(user) 71 | || !_ps->isUsernameAvailable(user)) 72 | alreadyexists.insert(user); 73 | else 74 | users.insert(make_pair(user, password)); 75 | } 76 | } 77 | 78 | if (!notwellformed.empty() || !alreadyexists.empty() || !duplicates.empty()) 79 | { 80 | string errmsg; 81 | 82 | if (!notwellformed.empty()) 83 | { 84 | errmsg += _("The following user names contain invalid characters: "); 85 | for (auto user : notwellformed) 86 | errmsg += "'"+user+"' "; 87 | 88 | errmsg += _("\nOnly the following characters are allowed: a-z 0-9 . - _\n"); 89 | } 90 | if (!alreadyexists.empty()) 91 | { 92 | errmsg += _("The following user names already exist on the system or this computer: "); 93 | for (auto user : alreadyexists) 94 | errmsg += "'"+user+"' "; 95 | errmsg += "\n"; 96 | } 97 | if (!duplicates.empty()) 98 | { 99 | errmsg += _("The following user names appear multiple times in the CSV file: "); 100 | for (auto user : duplicates) 101 | errmsg += "'"+user+"' "; 102 | } 103 | 104 | Gtk::MessageDialog d(errmsg); 105 | d.run(); 106 | } 107 | 108 | if (!users.empty()) 109 | { 110 | /* Replace existing input boxes with dynamically created ones */ 111 | 112 | for (int i=0; i<_userEntry.size(); i++) 113 | _grid->remove_row(1); 114 | _userEntry.clear(); 115 | _passEntry.clear(); 116 | 117 | int nr = 1; 118 | for (auto &kv : users) 119 | { 120 | auto label = Gtk::manage(new Gtk::Label( to_string(nr)+")" )); 121 | _grid->attach(*label, 0, nr, 1, 1); 122 | auto uentry = Gtk::manage(new Gtk::Entry() ); 123 | uentry->set_text(kv.first); 124 | _grid->attach(*uentry, 1, nr, 1, 1); 125 | _userEntry.push_back(uentry); 126 | uentry->signal_changed().connect(sigc::mem_fun(this, &AddUserDialog::setAddUserOkButton)); 127 | auto pentry = Gtk::manage(new Gtk::Entry() ); 128 | pentry->set_text(kv.second); 129 | pentry->set_visibility(false); 130 | _grid->attach(*pentry, 2, nr, 1, 1); 131 | _passEntry.push_back(pentry); 132 | pentry->signal_changed().connect(sigc::mem_fun(this, &AddUserDialog::setAddUserOkButton)); 133 | nr++; 134 | } 135 | _grid->show_all(); 136 | setAddUserOkButton(); 137 | 138 | return true; 139 | } 140 | } 141 | catch (const exception &e) 142 | { 143 | Gtk::MessageDialog d(e.what()); 144 | d.run(); 145 | } 146 | 147 | return false; 148 | } 149 | -------------------------------------------------------------------------------- /src/adduserdialog.h: -------------------------------------------------------------------------------- 1 | #ifndef ADDUSERDIALOG_H 2 | #define ADDUSERDIALOG_H 3 | 4 | #include "piserver.h" 5 | #include "abstractadduser.h" 6 | #include 7 | #include 8 | 9 | class AddUserDialog : private AbstractAddUser 10 | { 11 | public: 12 | AddUserDialog(PiServer *ps, Gtk::Window *parent); 13 | virtual ~AddUserDialog(); 14 | bool exec(); 15 | bool importCSV(const std::string &filename); 16 | 17 | protected: 18 | PiServer *_ps; 19 | Gtk::Window *_parentWindow; 20 | Gtk::Dialog *_dialog; 21 | Gtk::Button *_okbutton; 22 | Gtk::Grid *_grid; 23 | 24 | virtual void setAddUserOkButton(); 25 | }; 26 | 27 | #endif // ADDUSERDIALOG_H 28 | -------------------------------------------------------------------------------- /src/clonedistrodialog.cpp: -------------------------------------------------------------------------------- 1 | #include "clonedistrodialog.h" 2 | #include "distribution.h" 3 | #include "piserver.h" 4 | #include 5 | #include 6 | #include 7 | 8 | using namespace std; 9 | 10 | CloneDistroDialog::CloneDistroDialog(PiServer *ps, Distribution *olddistro, Gtk::Window *parent) 11 | : _ps(ps), _olddistro(olddistro) 12 | { 13 | auto builder = Gtk::Builder::create_from_resource("/data/clonedistrodialog.glade"); 14 | Gtk::Label *distrolabel; 15 | builder->get_widget("clonedistrodialog", _dialog); 16 | builder->get_widget("okbutton", _okButton); 17 | builder->get_widget("nameentry", _nameEntry); 18 | builder->get_widget("distrolabel", distrolabel); 19 | distrolabel->set_text(olddistro->name()); 20 | 21 | _nameEntry->signal_changed().connect(sigc::mem_fun(this, &CloneDistroDialog::setOkSensitive)); 22 | if (parent) 23 | _dialog->set_transient_for(*parent); 24 | } 25 | 26 | CloneDistroDialog::~CloneDistroDialog() 27 | { 28 | delete _dialog; 29 | } 30 | 31 | bool CloneDistroDialog::exec() 32 | { 33 | while (_dialog->run() == Gtk::RESPONSE_OK) 34 | { 35 | try 36 | { 37 | string name = _nameEntry->get_text(); 38 | 39 | 40 | if (_ps->getDistributions()->count(name)) 41 | throw runtime_error(Glib::ustring::compose( 42 | _("There already exists another OS with the name '%1'"), name)); 43 | 44 | Distribution *newdistro = new Distribution(name, ""); 45 | string oldpath = _olddistro->distroPath(); 46 | string newpath = newdistro->distroPath(); 47 | 48 | if (::access(oldpath.c_str(), F_OK) == -1) 49 | throw runtime_error(Glib::ustring::compose( 50 | _("Source folder '%1' does not exists"), oldpath)); 51 | if (::access(newpath.c_str(), F_OK) == 0) 52 | throw runtime_error(Glib::ustring::compose( 53 | _("There is already a folder '%1' on disk"), newpath)); 54 | 55 | /* FIXME: proper file copy code with disk space check and progress indication 56 | instead of just calling "cp" may be better */ 57 | 58 | const gchar *cmd[] = {"cp", "-a", "--one-file-system", oldpath.c_str(), newpath.c_str(), NULL}; 59 | GError *error = NULL; 60 | 61 | if (!g_spawn_async(NULL, (gchar **) cmd, NULL, 62 | (GSpawnFlags) (G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH), NULL, NULL, &_pid, &error)) 63 | throw runtime_error(error->message); 64 | 65 | _progressDialog = new Gtk::MessageDialog(*_dialog, _("Copying files"), 66 | false, Gtk::MESSAGE_OTHER, Gtk::BUTTONS_NONE, false); 67 | sigc::connection cwatch = Glib::signal_child_watch().connect(sigc::mem_fun(this, &CloneDistroDialog::onCopyFinished), _pid); 68 | _dialog->get_window()->set_cursor(Gdk::Cursor::create(Gdk::WATCH)); 69 | int result = _progressDialog->run(); 70 | _ps->addDistribution(newdistro); 71 | _dialog->get_window()->set_cursor(); 72 | cwatch.disconnect(); 73 | g_spawn_close_pid(_pid); 74 | delete _progressDialog; 75 | _progressDialog = NULL; 76 | 77 | if (result != 0) 78 | { 79 | const char *msg = _("Error occured while copying files"); 80 | 81 | if (result == Gtk::RESPONSE_DELETE_EVENT) 82 | { 83 | ::kill(_pid, SIGTERM); 84 | msg = _("Copying files cancelled"); 85 | } 86 | 87 | Gtk::MessageDialog d(msg); 88 | d.set_transient_for(*_dialog); 89 | d.run(); 90 | 91 | /* FIXME: should we delete partially copied files? */ 92 | } 93 | 94 | return true; 95 | } 96 | catch (const runtime_error &r) 97 | { 98 | Gtk::MessageDialog d(r.what()); 99 | d.set_transient_for(*_dialog); 100 | d.run(); 101 | } 102 | } 103 | 104 | return false; 105 | } 106 | 107 | bool CloneDistroDialog::isNameValid(const std::string &name) 108 | { 109 | if (name[0] == '-' || name[0] == '.' || name[0] == ' ') 110 | return false; 111 | 112 | for (char c : name) 113 | { 114 | if (!isalnum(c) && c != '.' && c != '_' && c != '-' && c != ' ') 115 | return false; 116 | } 117 | 118 | return true; 119 | } 120 | 121 | void CloneDistroDialog::setOkSensitive() 122 | { 123 | bool ok = _nameEntry->get_text_length() 124 | && isNameValid(_nameEntry->get_text() ); 125 | _okButton->set_sensitive(ok); 126 | } 127 | 128 | void CloneDistroDialog::onCopyFinished(GPid pid, int status) 129 | { 130 | if (pid == _pid && _progressDialog) 131 | _progressDialog->response(status); 132 | } 133 | -------------------------------------------------------------------------------- /src/clonedistrodialog.h: -------------------------------------------------------------------------------- 1 | #ifndef CLONEDISTRODIALOG_H 2 | #define CLONEDISTRODIALOG_H 3 | 4 | #include 5 | #include 6 | 7 | class PiServer; 8 | class Distribution; 9 | 10 | class CloneDistroDialog 11 | { 12 | public: 13 | CloneDistroDialog(PiServer *ps, Distribution *olddistro, Gtk::Window *parent); 14 | virtual ~CloneDistroDialog(); 15 | bool exec(); 16 | 17 | protected: 18 | PiServer *_ps; 19 | Distribution *_olddistro; 20 | Gtk::Dialog *_dialog, *_progressDialog; 21 | Gtk::Button *_okButton; 22 | Gtk::Entry *_nameEntry; 23 | GPid _pid; 24 | 25 | bool isNameValid(const std::string &name); 26 | void setOkSensitive(); 27 | void onCopyFinished(GPid pid, int status); 28 | }; 29 | 30 | #endif // CLONEDISTRODIALOG_H 31 | -------------------------------------------------------------------------------- /src/config.h: -------------------------------------------------------------------------------- 1 | #ifndef CONFIG_H 2 | #define CONFIG_H 3 | 4 | #ifndef PISERVER_DATADIR 5 | #define PISERVER_DATADIR "/var/lib/piserver" 6 | #endif 7 | 8 | #define PACKAGE "piserver" 9 | 10 | #define PISERVER_HOSTSFILE PISERVER_DATADIR "/hosts.json" 11 | #define PISERVER_DISTROFILE PISERVER_DATADIR "/installed_distros.json" 12 | #define PISERVER_SETTINGSFILE PISERVER_DATADIR "/settings.json" 13 | #define PISERVER_TFTPROOT PISERVER_DATADIR "/tftproot" 14 | #define PISERVER_DISTROROOT PISERVER_DATADIR "/os" 15 | #define PISERVER_SHAREDROOT PISERVER_DISTROROOT "/shared" 16 | #define PISERVER_HOMEROOT "/home" 17 | #define PISERVER_DNSMASQCONFFILE "/etc/dnsmasq.d/piserver" 18 | #define PISERVER_REPO_URL "http://downloads.raspberrypi.org/piserver.json" 19 | #define PISERVER_REPO_CACHEFILE PISERVER_DATADIR "/avail_distros_cache.json" 20 | #define PISERVER_POSTINSTALLSCRIPT PISERVER_DATADIR "/scripts/postinstall.sh" 21 | #define PISERVER_CONVERTSCRIPT PISERVER_DATADIR "/scripts/convert_folder.sh" 22 | 23 | #define OUI_FILTER "^(b8:27:eb|dc:a6:32|e4:5f:01|d8:3a:dd):" 24 | #define DHCPANALYZER_FILTER_STRING "PXEClient" 25 | 26 | #define PISERVER_LDAP_URL "ldapi:///" 27 | #define PISERVER_DOMAIN "raspberrypi.local" 28 | #define PISERVER_LDAP_DN "dc=raspberrypi,dc=local" 29 | 30 | #define PISERVER_LDAP_USER "cn=admin,dc=raspberrypi,dc=local" 31 | 32 | #define PISERVER_LDAP_TIMEOUT 10 33 | #define PISERVER_MIN_UID 10000 34 | #define PISERVER_GID 100 35 | #define PISERVER_SHELL "/bin/bash" 36 | 37 | #endif // CONFIG_H 38 | -------------------------------------------------------------------------------- /src/dependenciesinstallthread.cpp: -------------------------------------------------------------------------------- 1 | #include "dependenciesinstallthread.h" 2 | #include "config.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | using namespace std; 10 | 11 | DependenciesInstallThread::DependenciesInstallThread(PiServer *ps) 12 | : _ps(ps), _thread(NULL) 13 | { 14 | } 15 | 16 | DependenciesInstallThread::~DependenciesInstallThread() 17 | { 18 | if (_thread) 19 | { 20 | _thread->join(); 21 | delete _thread; 22 | } 23 | } 24 | 25 | void DependenciesInstallThread::start() 26 | { 27 | _thread = new thread(&DependenciesInstallThread::run, this); 28 | } 29 | 30 | void DependenciesInstallThread::run() 31 | { 32 | string ldapUri = _ps->getSetting("ldapUri", PISERVER_LDAP_URL); 33 | string ldapDN = _ps->getSetting("ldapDN", PISERVER_LDAP_DN); 34 | string ldapServerType = _ps->getSetting("ldapServerType"); 35 | string ldapUser = _ps->getSetting("ldapUser"); 36 | string ldapPassword = _ps->getSetting("ldapPassword"); 37 | string ldapExtraConfig = _ps->getSetting("ldapExtraConfig"); 38 | string pkglist = "dnsmasq openssh-server nfs-kernel-server libnss-ldapd libpam-ldapd ldap-utils gnutls-bin libarchive-tools ntp"; 39 | bool installSlapd = ldapServerType.empty(); 40 | bool nslcdAlreadyExists = ::access("/etc/nslcd.conf", F_OK) != -1; 41 | bool slapdAlreadyExists = ::access("/etc/ldap/slapd.d", F_OK) != -1; 42 | 43 | try 44 | { 45 | _execCheckResult("apt-get update -q"); 46 | 47 | if (installSlapd) 48 | { 49 | if (slapdAlreadyExists) 50 | { 51 | cerr << "Force removal of existing slapd" << endl; 52 | _execCheckResult("apt-get -q -y remove --purge slapd"); 53 | } 54 | 55 | _preseed({ 56 | {"slapd slapd/password1 password", ldapPassword}, 57 | {"slapd slapd/internal/adminpw password", ldapPassword}, 58 | {"slapd slapd/internal/generated_adminpw password", ldapPassword}, 59 | {"slapd slapd/password2 password", ldapPassword}, 60 | {"slapd slapd/allow_ldap_v2 boolean", "false"}, 61 | {"slapd slapd/password_mismatch", "note"}, 62 | {"slapd slapd/invalid_config boolean", "true"}, 63 | {"slapd shared/organization string", PISERVER_DOMAIN}, 64 | {"slapd slapd/upgrade_slapcat_failure", "error"}, 65 | {"slapd slapd/no_configuration boolean", "false"}, 66 | {"slapd slapd/move_old_database boolean", "true"}, 67 | {"slapd slapd/dump_database_destdir string", "/var/backups/slapd-VERSION"}, 68 | {"slapd slapd/purge_database boolean", "false"}, 69 | {"slapd slapd/domain string", PISERVER_DOMAIN}, 70 | {"slapd slapd/backend select", "MDB"}, 71 | {"slapd slapd/dump_database select", "when needed"} 72 | }); 73 | pkglist += " slapd"; 74 | } 75 | 76 | /* Install pam_mkhomedir to create home on first login */ 77 | _execCheckResult("cp " PISERVER_DATADIR "/scripts/mkhomedir-piserver /usr/share/pam-configs"); 78 | _execCheckResult("pam-auth-update --package"); 79 | 80 | map nslcdPreseed = { 81 | {"nslcd nslcd/ldap-uris string", ldapUri}, 82 | {"nslcd nslcd/ldap-base string", ldapDN}, 83 | {"libnss-ldapd libnss-ldapd/nsswitch multiselect", "group, passwd, shadow"}, 84 | {"nslcd nslcd/ldap-auth-type select", "none"} 85 | }; 86 | if (!ldapServerType.empty()) 87 | { 88 | if (!ldapUser.empty()) 89 | { 90 | nslcdPreseed["nslcd nslcd/ldap-auth-type select"] = "simple"; 91 | nslcdPreseed.insert(make_pair("nslcd nslcd/ldap-binddn string", ldapUser)); 92 | nslcdPreseed.insert(make_pair("nslcd nslcd/ldap-bindpw string", ldapPassword)); 93 | } 94 | } 95 | _preseed(nslcdPreseed); 96 | 97 | ::setenv("DEBIAN_FRONTEND", "noninteractive", 1); 98 | _execCheckResult("apt-get -q -y install "+pkglist); 99 | 100 | if (nslcdAlreadyExists) 101 | { 102 | cerr << "Force reconfiguration of existing nslcd" << endl; 103 | ::unlink("/etc/nslcd.conf"); 104 | _execCheckResult("dpkg-reconfigure nslcd"); 105 | } 106 | ::unsetenv("DEBIAN_FRONTEND"); 107 | 108 | if (installSlapd) 109 | { 110 | // Using gnutls's certtool instead of OpenSSL, because it allows setting start date to 1970 111 | _execCheckResult("certtool --generate-privkey --ecc --outfile /etc/ldap/piserver.key"); 112 | ofstream os("/etc/ldap/piserver.tpl"); 113 | os << "cn = \"piserver\"" << endl 114 | << "tls_www_server" << endl 115 | << "activation_date=\"1970-01-01 00:00:00 UTC\"" << endl 116 | << "expiration_days=\"-1\"" << endl; 117 | os.close(); 118 | _execCheckResult("certtool --generate-self-signed --load-privkey /etc/ldap/piserver.key --template=/etc/ldap/piserver.tpl --outfile=/etc/ssl/certs/piserver.pem"); 119 | _execCheckResult("chown openldap /etc/ldap/piserver.key"); 120 | if (::unlink("/etc/ldap/piserver.tpl") != 0) { } 121 | 122 | const char *ldif = 123 | "dn: cn=config\n" 124 | "changetype: modify\n" 125 | "replace: olcTLSCertificateFile\n" 126 | "olcTLSCertificateFile: /etc/ssl/certs/piserver.pem\n" 127 | "-\n" 128 | "replace: olcTLSCACertificateFile\n" 129 | "olcTLSCACertificateFile: /etc/ssl/certs/piserver.pem\n" 130 | "-\n" 131 | "replace: olcTLSCertificateKeyFile\n" 132 | "olcTLSCertificateKeyFile: /etc/ldap/piserver.key\n"; 133 | FILE *f = popen("ldapmodify -Y EXTERNAL -H ldapi:///", "w"); 134 | fwrite(ldif, strlen(ldif), 1, f); 135 | pclose(f); 136 | 137 | ldif = 138 | "dn: cn=module{0},cn=config\n" 139 | "changetype: modify\n" 140 | "add: olcModuleload\n" 141 | "olcModuleLoad: lastbind.la\n" 142 | "\n" 143 | "dn: olcOverlay=lastbind,olcDatabase={1}mdb,cn=config\n" 144 | "changetype: add\n" 145 | "objectClass: olcLastBindConfig\n" 146 | "objectClass: olcOverlayConfig\n" 147 | "objectClass: top\n" 148 | "olcOverlay: lastbind\n" 149 | "olcLastBindPrecision: 60\n"; 150 | f = popen("ldapmodify -Y EXTERNAL -H ldapi:///", "w"); 151 | fwrite(ldif, strlen(ldif), 1, f); 152 | pclose(f); 153 | 154 | // Test that LDAP server works. Generates exception if not 155 | _ps->isUsernameAvailable("test"); 156 | } 157 | 158 | if (!ldapExtraConfig.empty()) 159 | { 160 | ofstream o("/etc/nslcd.conf", ios_base::out | ios_base::binary | ios_base::app); 161 | o.write(ldapExtraConfig.c_str(), ldapExtraConfig.size()); 162 | o.close(); 163 | _execCheckResult("systemctl restart nslcd"); 164 | } 165 | 166 | _execCheckResult("systemctl start piserver_ssh"); 167 | _execCheckResult("systemctl enable piserver_ssh"); 168 | _execCheckResult("systemctl enable rpcbind"); 169 | _ps->addToExports(PISERVER_DISTROROOT " *(ro,no_subtree_check,no_root_squash,fsid=1055)"); 170 | _ps->regenDnsmasqConf(); 171 | 172 | // On Debian Stretch it seems necessary to restart nscd before logging in with LDAP works 173 | if ( ::system("systemctl restart nscd") != 0 ) { } 174 | 175 | ::sync(); 176 | //std::this_thread::sleep_for(chrono::seconds(1)); 177 | _signalSucess.emit(); 178 | } 179 | catch (exception &e) 180 | { 181 | _error = e.what(); 182 | _signalFailure.emit(); 183 | } 184 | } 185 | 186 | void DependenciesInstallThread::_execCheckResult(const std::string &cmd) 187 | { 188 | if (::system(cmd.c_str()) != 0) 189 | { 190 | throw runtime_error("Error executing '"+cmd+"'"); 191 | } 192 | } 193 | 194 | void DependenciesInstallThread::_preseed(const std::map &values) 195 | { 196 | FILE *f = popen("/usr/bin/debconf-set-selections", "w"); 197 | if (f) 198 | { 199 | for (auto kv : values) 200 | { 201 | string s = kv.first+" "+kv.second+"\n"; 202 | fwrite(s.c_str(), s.size(), 1, f); 203 | } 204 | 205 | pclose(f); 206 | } 207 | } 208 | 209 | Glib::Dispatcher &DependenciesInstallThread::signalSuccess() 210 | { 211 | return _signalSucess; 212 | } 213 | 214 | Glib::Dispatcher &DependenciesInstallThread::signalFailure() 215 | { 216 | return _signalFailure; 217 | } 218 | 219 | std::string DependenciesInstallThread::error() 220 | { 221 | return _error; 222 | } 223 | -------------------------------------------------------------------------------- /src/dependenciesinstallthread.h: -------------------------------------------------------------------------------- 1 | #ifndef DEPENDENCIESINSTALLTHREAD_H 2 | #define DEPENDENCIESINSTALLTHREAD_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "piserver.h" 9 | 10 | class DependenciesInstallThread 11 | { 12 | public: 13 | DependenciesInstallThread(PiServer *ps); 14 | virtual ~DependenciesInstallThread(); 15 | 16 | /* 17 | * Thread start function 18 | */ 19 | void start(); 20 | 21 | /* 22 | * Thread safe signals 23 | * Creator of the DownloadThread object must have a glib main loop 24 | */ 25 | Glib::Dispatcher &signalSuccess(); 26 | Glib::Dispatcher &signalFailure(); 27 | std::string error(); 28 | 29 | protected: 30 | virtual void run(); 31 | void _execCheckResult(const std::string &cmd); 32 | void _preseed(const std::map &values); 33 | 34 | PiServer *_ps; 35 | std::string _error; 36 | std::thread *_thread; 37 | Glib::Dispatcher _signalSucess, _signalFailure; 38 | }; 39 | 40 | #endif // DEPENDENCIESINSTALLTHREAD_H 41 | -------------------------------------------------------------------------------- /src/dhcp.h: -------------------------------------------------------------------------------- 1 | #ifndef DHCP_H 2 | #define DHCP_H 3 | 4 | #include 5 | 6 | struct dhcp_packet { 7 | uint8_t op, htype, hlen, hops; 8 | uint32_t xid; 9 | uint16_t secs, flags; 10 | uint32_t ciaddr, yiaddr, siaddr, giaddr; 11 | unsigned char chaddr[16], legacy[192]; 12 | uint32_t cookie; 13 | unsigned char data[500]; 14 | } __attribute__((__packed__)); 15 | 16 | #define DHCP_COOKIE htonl(0x63825363) 17 | 18 | #endif // DHCP_H 19 | -------------------------------------------------------------------------------- /src/dhcpanalyzer.cpp: -------------------------------------------------------------------------------- 1 | #include "dhcpanalyzer.h" 2 | #include "piserver.h" 3 | #include "dhcp.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | DhcpAnalyzer::DhcpAnalyzer() 14 | : _s(-1) 15 | { 16 | } 17 | 18 | DhcpAnalyzer::~DhcpAnalyzer() 19 | { 20 | if (_s != -1) 21 | { 22 | _conn.disconnect(); 23 | close(_s); 24 | } 25 | } 26 | 27 | void DhcpAnalyzer::startListening() 28 | { 29 | struct sockaddr_in a = { 0 }; 30 | int flag = 1; 31 | 32 | a.sin_family = AF_INET; 33 | a.sin_port = htons(67); 34 | a.sin_addr.s_addr = INADDR_ANY; 35 | 36 | _s = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP); 37 | if (_s < 0) 38 | throw std::runtime_error("Error creating socket"); 39 | 40 | setsockopt(_s, SOL_SOCKET, SO_REUSEADDR, (char *) &flag, sizeof(flag)); 41 | setsockopt(_s, SOL_SOCKET, SO_BROADCAST, (char *) &flag, sizeof(flag)); 42 | 43 | if (bind(_s, (struct sockaddr *) &a, sizeof(a)) < 0) 44 | { 45 | close(_s); 46 | throw std::runtime_error("Error binding to UDP port 67"); 47 | } 48 | 49 | _conn = Glib::signal_io().connect(sigc::mem_fun(this, &DhcpAnalyzer::on_packet), _s, Glib::IO_IN); 50 | } 51 | 52 | bool DhcpAnalyzer::on_packet(Glib::IOCondition) 53 | { 54 | #ifdef OUI_FILTER 55 | static std::regex r(OUI_FILTER); 56 | #endif 57 | struct dhcp_packet dhcp = { 0 }; 58 | char macbuf[32]; 59 | 60 | int len = recvfrom(_s, &dhcp, sizeof(dhcp), 0, NULL, 0); 61 | 62 | if ( len > 0 63 | && dhcp.op == 1 64 | && dhcp.cookie == DHCP_COOKIE 65 | #ifdef DHCPANALYZER_FILTER_STRING 66 | && memmem(dhcp.data, sizeof(dhcp.data), DHCPANALYZER_FILTER_STRING, strlen(DHCPANALYZER_FILTER_STRING)) != NULL 67 | #endif 68 | ) 69 | { 70 | snprintf(macbuf, sizeof(macbuf), "%.2x:%.2x:%.2x:%.2x:%.2x:%.2x", 71 | dhcp.chaddr[0], dhcp.chaddr[1], dhcp.chaddr[2], 72 | dhcp.chaddr[3], dhcp.chaddr[4], dhcp.chaddr[5]); 73 | std::string mac = macbuf; 74 | 75 | #ifdef OUI_FILTER 76 | if (!regex_search(mac, r)) 77 | return true; 78 | #endif 79 | 80 | if (!_seen.count(mac)) 81 | { 82 | _seen.insert(mac); 83 | _signal_macDetected.emit(mac); 84 | } 85 | } 86 | 87 | return true; 88 | } 89 | -------------------------------------------------------------------------------- /src/dhcpanalyzer.h: -------------------------------------------------------------------------------- 1 | #ifndef DHCPANALYZER_H 2 | #define DHCPANALYZER_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | class DhcpAnalyzer 9 | { 10 | public: 11 | DhcpAnalyzer(); 12 | virtual ~DhcpAnalyzer(); 13 | void startListening(); 14 | 15 | /* signals */ 16 | inline sigc::signal signal_macDetected() 17 | { 18 | return _signal_macDetected; 19 | } 20 | 21 | protected: 22 | int _s; 23 | sigc::connection _conn; 24 | std::set _seen; 25 | sigc::signal _signal_macDetected; 26 | 27 | /* Slots */ 28 | bool on_packet(Glib::IOCondition cond); 29 | }; 30 | 31 | #endif // DHCPANALYZER_H 32 | -------------------------------------------------------------------------------- /src/dhcpclient.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * DHCP client class 3 | * Serves two purposes: 4 | * 5 | * - Double check no other DHCP servers are active, if user wants to run standalone mode 6 | * - Query DHCP server to autodetect domain name in use. Makes setting up active directory authentication easier 7 | */ 8 | 9 | #include "dhcpclient.h" 10 | #include "dhcp.h" 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | DhcpClient::DhcpClient() 18 | : _s(-1), _timeoutInSeconds(3) 19 | { 20 | } 21 | 22 | DhcpClient::~DhcpClient() 23 | { 24 | if (_timer.connected()) 25 | { 26 | _timer.disconnect(); 27 | } 28 | if (_s != -1) 29 | { 30 | _conn.disconnect(); 31 | close(_s); 32 | } 33 | } 34 | 35 | void DhcpClient::sendDhcpDiscover(const std::string &networkInterface) 36 | { 37 | if (_s == -1) 38 | { 39 | struct sockaddr_in a = { 0 }; 40 | int flag = 1; 41 | 42 | a.sin_family = AF_INET; 43 | a.sin_port = htons(68); 44 | a.sin_addr.s_addr = INADDR_ANY; 45 | 46 | _s = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP); 47 | if (_s < 0) 48 | throw std::runtime_error("Error creating socket"); 49 | 50 | setsockopt(_s, SOL_SOCKET, SO_REUSEADDR, (char *) &flag, sizeof(flag)); 51 | setsockopt(_s, SOL_SOCKET, SO_BROADCAST, (char *) &flag, sizeof(flag)); 52 | setsockopt(_s, SOL_SOCKET, SO_BINDTODEVICE, networkInterface.c_str(), networkInterface.size()); 53 | 54 | if (bind(_s, (struct sockaddr *) &a, sizeof(a)) < 0) 55 | { 56 | close(_s); 57 | throw std::runtime_error("Error binding to UDP port 68"); 58 | } 59 | 60 | _conn = Glib::signal_io().connect(sigc::mem_fun(this, &DhcpClient::on_packet), _s, Glib::IO_IN); 61 | } 62 | 63 | struct dhcp_packet discover = { 0 }; 64 | int i = 0; 65 | discover.op = 1; 66 | discover.htype = 1; 67 | discover.hlen = 6; 68 | discover.secs = htons(60); 69 | discover.flags = htons(0x8000); /* Broadcast */ 70 | discover.chaddr[0] = 0x31; /* Dummy MAC */ 71 | discover.chaddr[1] = 0x32; 72 | discover.chaddr[2] = 0x33; 73 | discover.chaddr[3] = 0x34; 74 | discover.chaddr[4] = 0x35; 75 | discover.chaddr[5] = 0x36; 76 | discover.cookie = DHCP_COOKIE; 77 | /* DHCP options */ 78 | discover.data[i++] = 53; /* Message type */ 79 | discover.data[i++] = 1; 80 | discover.data[i++] = 1; /* Discover */ 81 | discover.data[i++] = 61; /* Client identifier */ 82 | discover.data[i++] = 7; 83 | discover.data[i++] = 1; 84 | for (int j=0; j<6; j++) 85 | { 86 | discover.data[i++] = discover.chaddr[j]; 87 | } 88 | discover.data[i++] = 55; /* Parameter request list */ 89 | discover.data[i++] = 1; 90 | discover.data[i++] = 15; /* We would like to know the domain */ 91 | discover.data[i++] = 0xFF; 92 | 93 | size_t len = sizeof(discover) - sizeof(discover.data) + i; 94 | struct sockaddr_in addr = {0}; 95 | addr.sin_family = AF_INET; 96 | addr.sin_port = htons(67); 97 | addr.sin_addr.s_addr = htonl(INADDR_BROADCAST); 98 | 99 | if (sendto(_s, &discover, len, 0, (struct sockaddr *) &addr, sizeof(addr)) == -1) 100 | { 101 | throw std::runtime_error("Error sending DHCPDISCOVER packet"); 102 | } 103 | 104 | _haveOffer = false; 105 | _timer = Glib::signal_timeout().connect_seconds( sigc::mem_fun(this, &DhcpClient::on_timeout), _timeoutInSeconds); 106 | } 107 | 108 | bool DhcpClient::on_packet(Glib::IOCondition) 109 | { 110 | struct sockaddr_storage addr = { 0 }; 111 | socklen_t addrlen = sizeof(addr); 112 | char ipstr[INET_ADDRSTRLEN] = { 0 }; 113 | struct dhcp_packet dhcp = { 0 }; 114 | int len = recvfrom(_s, &dhcp, sizeof(dhcp), 0, (struct sockaddr *) &addr, &addrlen); 115 | 116 | if ( len > 0 117 | && dhcp.op == 2 118 | && dhcp.cookie == DHCP_COOKIE 119 | && addr.ss_family == AF_INET 120 | ) 121 | { 122 | _haveOffer = true; 123 | struct sockaddr_in *addr4 = (struct sockaddr_in *) &addr; 124 | inet_ntop(AF_INET, &addr4->sin_addr, ipstr, sizeof(ipstr)); 125 | _signal_dhcpOffer.emit(ipstr); 126 | 127 | /* Parse options looking for domain */ 128 | int optlen = len - (sizeof(dhcp) - sizeof(dhcp.data)); 129 | if (optlen > 0) 130 | _parseDhcpOptions(dhcp.data, optlen); 131 | } 132 | 133 | return true; 134 | } 135 | 136 | bool DhcpClient::on_timeout() 137 | { 138 | _conn.disconnect(); 139 | close(_s); 140 | _s = -1; 141 | 142 | if (!_haveOffer) 143 | _signal_timeout.emit(); 144 | 145 | return false; 146 | } 147 | 148 | /* Parse DHCP options to look for our domain name */ 149 | void DhcpClient::_parseDhcpOptions(const unsigned char *buf, int len) 150 | { 151 | int i = 0; 152 | 153 | while ((i+1) < len) 154 | { 155 | int opt = buf[i++]; 156 | 157 | if (opt == 0xFF) /* End of options */ 158 | break; 159 | if (opt == 0) /* Padding */ 160 | continue; 161 | 162 | int optlen = buf[i++]; 163 | if (i+optlen >= len) 164 | { 165 | break; /* Option beyond length of packet. TODO: parse overload space as well */ 166 | } 167 | 168 | if (opt == 15 && optlen) /* Domain */ 169 | { 170 | std::string domain((char *) &buf[i], optlen); 171 | if (domain.back() == '\0') 172 | domain.pop_back(); 173 | 174 | if (!domain.empty()) 175 | { 176 | _signal_dhcpOfferDomain.emit(domain); 177 | } 178 | } 179 | 180 | i += optlen; 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/dhcpclient.h: -------------------------------------------------------------------------------- 1 | #ifndef DHCPCLIENT_H 2 | #define DHCPCLIENT_H 3 | 4 | /** 5 | * DHCP client class 6 | * Serves two purposes: 7 | * 8 | * - Double check no other DHCP servers are active, if user wants to run standalone mode 9 | * - Query DHCP server to autodetect domain name in use. Makes setting up active directory authentication easier 10 | */ 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | class DhcpClient 17 | { 18 | public: 19 | DhcpClient(); 20 | virtual ~DhcpClient(); 21 | void sendDhcpDiscover(const std::string &networkInterface); 22 | 23 | /* signals */ 24 | inline sigc::signal signal_dhcpOffer() 25 | { 26 | return _signal_dhcpOffer; 27 | } 28 | 29 | inline sigc::signal signal_dhcpOfferDomain() 30 | { 31 | return _signal_dhcpOfferDomain; 32 | } 33 | 34 | 35 | inline sigc::signal signal_timeout() 36 | { 37 | return _signal_timeout; 38 | } 39 | 40 | protected: 41 | int _s, _timeoutInSeconds; 42 | bool _haveOffer; 43 | sigc::connection _conn; 44 | sigc::signal _signal_dhcpOffer; 45 | sigc::signal _signal_dhcpOfferDomain; 46 | sigc::signal _signal_timeout; 47 | sigc::connection _timer; 48 | 49 | void _parseDhcpOptions(const unsigned char *buf, int len); 50 | 51 | /* Slots */ 52 | bool on_packet(Glib::IOCondition cond); 53 | bool on_timeout(); 54 | }; 55 | 56 | #endif // DHCPCLIENT_H 57 | -------------------------------------------------------------------------------- /src/distribution.cpp: -------------------------------------------------------------------------------- 1 | #include "distribution.h" 2 | #include "piserver.h" 3 | #include 4 | 5 | Distribution::Distribution(const std::string &name, const std::string &version, const std::string &path) 6 | : _name(name), _version(version), _path(path) 7 | { 8 | if (path.empty()) 9 | { 10 | _path = std::string(PISERVER_DISTROROOT)+"/"+_name; 11 | if (!_version.empty()) 12 | _path += "-"+_version; 13 | 14 | replace(_path.begin(), _path.end(), ' ', '_'); 15 | } 16 | } 17 | 18 | std::string Distribution::distroPath() 19 | { 20 | return _path; 21 | } 22 | 23 | void Distribution::setName(const std::string &name) 24 | { 25 | _name = name; 26 | } 27 | 28 | void Distribution::setVersion(const std::string &version) 29 | { 30 | _version = version; 31 | } 32 | 33 | void Distribution::setLatestVersion(const std::string &latest) 34 | { 35 | _latestVersion = latest; 36 | } 37 | -------------------------------------------------------------------------------- /src/distribution.h: -------------------------------------------------------------------------------- 1 | #ifndef DISTRIBUTION_H 2 | #define DISTRIBUTION_H 3 | 4 | #include 5 | 6 | class Distribution 7 | { 8 | public: 9 | Distribution(const std::string &name, const std::string &version, const std::string &path = ""); 10 | std::string distroPath(); 11 | void setName(const std::string &name); 12 | void setVersion(const std::string &name); 13 | void setLatestVersion(const std::string &latest); 14 | 15 | inline const std::string &name() 16 | { 17 | return _name; 18 | } 19 | 20 | inline const std::string &version() 21 | { 22 | return _version; 23 | } 24 | 25 | inline const std::string &latestVersion() 26 | { 27 | return _latestVersion; 28 | } 29 | 30 | 31 | protected: 32 | std::string _name, _version, _latestVersion, _path; 33 | }; 34 | 35 | #endif // DISTRIBUTION_H 36 | -------------------------------------------------------------------------------- /src/downloadextractthread.cpp: -------------------------------------------------------------------------------- 1 | #include "downloadextractthread.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | using namespace std; 11 | 12 | const int DownloadExtractThread::MAX_QUEUE_SIZE = 64; 13 | 14 | DownloadExtractThread::DownloadExtractThread(const std::string &url, const std::string &localfolder) 15 | : DownloadThread(url), _folder(localfolder), _extractThread(NULL) 16 | { 17 | if (::mkdir(localfolder.c_str(), 0755) == -1) { /* Ignore errors. Folder may already exist */ } 18 | if (_folder.back() != '/') 19 | _folder += '/'; 20 | } 21 | 22 | DownloadExtractThread::~DownloadExtractThread() 23 | { 24 | if (_extractThread) 25 | { 26 | _extractThread->join(); 27 | delete _extractThread; 28 | } 29 | } 30 | 31 | size_t DownloadExtractThread::_writeData(const char *buf, size_t len) 32 | { 33 | if (!_extractThread) 34 | { 35 | /* Extract thread is started when first data comes in */ 36 | _extractThread = new thread(&DownloadExtractThread::extractRun, this); 37 | } 38 | 39 | _pushQueue(buf, len); 40 | 41 | return len; 42 | } 43 | 44 | void DownloadExtractThread::_onDownloadSuccess() 45 | { 46 | _pushQueue("", 0); 47 | } 48 | 49 | void DownloadExtractThread::_onDownloadError(const std::string &msg) 50 | { 51 | DownloadThread::_onDownloadError(msg); 52 | _cancelExtract(); 53 | } 54 | 55 | void DownloadExtractThread::_cancelExtract() 56 | { 57 | std::unique_lock lock(_queueMutex); 58 | _queue.clear(); 59 | _queue.push_back(string()); 60 | } 61 | 62 | void DownloadExtractThread::cancelDownload() 63 | { 64 | DownloadThread::cancelDownload(); 65 | _cancelExtract(); 66 | } 67 | 68 | /* Raise exception on libarchive errors */ 69 | static inline void _checkResult(int r, struct archive *a) 70 | { 71 | if (r < ARCHIVE_OK) 72 | /* Warning */ 73 | cerr << archive_error_string(a) << endl; 74 | if (r < ARCHIVE_WARN) 75 | /* Fatal */ 76 | throw runtime_error(archive_error_string(a)); 77 | } 78 | 79 | /* libarchive thread */ 80 | void DownloadExtractThread::extractRun() 81 | { 82 | char cwd[PATH_MAX] = {0}, *pcwd; 83 | struct archive *a = archive_read_new(); 84 | struct archive *ext = archive_write_disk_new(); 85 | struct archive_entry *entry; 86 | int r, flags = ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_PERM 87 | | ARCHIVE_EXTRACT_ACL | ARCHIVE_EXTRACT_FFLAGS /*| ARCHIVE_EXTRACT_XATTR*/; 88 | if (::getuid() == 0) 89 | flags |= ARCHIVE_EXTRACT_OWNER; 90 | 91 | pcwd = ::getcwd(cwd, sizeof(cwd)); 92 | if (::chdir(_folder.c_str()) == -1) 93 | { 94 | DownloadThread::cancelDownload(); 95 | _emitError(string("Error changing to directory '")+_folder+"': "+strerror(errno)); 96 | return; 97 | } 98 | 99 | archive_read_support_filter_all(a); 100 | archive_read_support_format_all(a); 101 | archive_write_disk_set_options(ext, flags); 102 | archive_read_open(a, this, NULL, &DownloadExtractThread::_archive_read, &DownloadExtractThread::_archive_close); 103 | 104 | try 105 | { 106 | while ( (r = archive_read_next_header(a, &entry)) != ARCHIVE_EOF) 107 | { 108 | _checkResult(r, a); 109 | //string fullpath = _folder+archive_entry_pathname(entry); 110 | //archive_entry_set_pathname(entry, fullpath.c_str()); 111 | r = archive_write_header(ext, entry); 112 | if (r < ARCHIVE_OK) 113 | cerr << archive_error_string(ext) << endl; 114 | else if (archive_entry_size(entry) > 0) 115 | { 116 | //checkResult(copyData(a, ext), a); 117 | const void *buff; 118 | size_t size; 119 | int64_t offset; 120 | 121 | while ( (r = archive_read_data_block(a, &buff, &size, &offset)) != ARCHIVE_EOF) 122 | { 123 | _checkResult(r, a); 124 | _checkResult(archive_write_data_block(ext, buff, size, offset), ext); 125 | } 126 | } 127 | _checkResult(archive_write_finish_entry(ext), ext); 128 | } 129 | 130 | if (!_postInstallScript.empty()) 131 | { 132 | ::setenv("DISTROROOT", _folder.c_str(), 1); 133 | if (!_ldapConfig.empty()) 134 | ::setenv("EXTERNAL_LDAP_CONFIG", _ldapConfig.c_str(), 1); 135 | if (system(_postInstallScript.c_str()) != 0) 136 | { 137 | _emitError("Error running post-installation script"); 138 | } 139 | else 140 | { 141 | _emitSuccess(); 142 | } 143 | ::unsetenv("DISTROROOT"); 144 | if (!_ldapConfig.empty()) 145 | ::unsetenv("EXTERNAL_LDAP_CONFIG"); 146 | } 147 | else 148 | { 149 | _emitSuccess(); 150 | } 151 | } 152 | catch (exception &e) 153 | { 154 | if (!_cancelled) 155 | { 156 | /* Fatal error */ 157 | DownloadThread::cancelDownload(); 158 | _emitError(string("Error extracting archive: ")+e.what()); 159 | } 160 | } 161 | 162 | archive_read_free(a); 163 | archive_write_free(ext); 164 | if (pcwd) 165 | { 166 | if (::chdir(pcwd) == -1) { } 167 | } 168 | } 169 | 170 | ssize_t DownloadExtractThread::_on_read(struct archive *a, const void **buff) 171 | { 172 | _buf = _popQueue(); 173 | *buff = _buf.c_str(); 174 | return _buf.size(); 175 | } 176 | 177 | int DownloadExtractThread::_on_close(struct archive *a) 178 | { 179 | return 0; 180 | } 181 | 182 | /* static callback functions that call object oriented equivalents */ 183 | ssize_t DownloadExtractThread::_archive_read(struct archive *a, void *client_data, const void **buff) 184 | { 185 | return static_cast(client_data)->_on_read(a, buff); 186 | } 187 | 188 | int DownloadExtractThread::_archive_close(struct archive *a, void *client_data) 189 | { 190 | return static_cast(client_data)->_on_close(a); 191 | } 192 | 193 | 194 | /* Synchronized queue using monitor consumer/producer pattern */ 195 | std::string DownloadExtractThread::_popQueue() 196 | { 197 | std::unique_lock lock(_queueMutex); 198 | 199 | _cv.wait(lock, [this]{ 200 | return _queue.size() != 0; 201 | }); 202 | 203 | string result = _queue.front(); 204 | _queue.pop_front(); 205 | 206 | if (_queue.size() == (MAX_QUEUE_SIZE-1)) 207 | { 208 | lock.unlock(); 209 | _cv.notify_one(); 210 | } 211 | 212 | return result; 213 | } 214 | 215 | void DownloadExtractThread::_pushQueue(const char *data, size_t len) 216 | { 217 | std::unique_lock lock(_queueMutex); 218 | 219 | _cv.wait(lock, [this]{ 220 | return _queue.size() != MAX_QUEUE_SIZE; 221 | }); 222 | 223 | _queue.emplace_back(data, len); 224 | string s(data, len); 225 | 226 | if (_queue.size() == 1) 227 | { 228 | lock.unlock(); 229 | _cv.notify_one(); 230 | } 231 | } 232 | 233 | void DownloadExtractThread::setPostInstallScript(const std::string &path) 234 | { 235 | _postInstallScript = path; 236 | } 237 | 238 | void DownloadExtractThread::setLdapConfig(const std::string &config) 239 | { 240 | _ldapConfig = config; 241 | } 242 | -------------------------------------------------------------------------------- /src/downloadextractthread.h: -------------------------------------------------------------------------------- 1 | #ifndef DOWNLOADEXTRACTTHREAD_H 2 | #define DOWNLOADEXTRACTTHREAD_H 3 | 4 | #include "downloadthread.h" 5 | #include 6 | #include 7 | 8 | class DownloadExtractThread : public DownloadThread 9 | { 10 | public: 11 | /* 12 | * Constructor 13 | * 14 | * - url: URL to download 15 | * - localfolder: Folder to extract archive to 16 | */ 17 | explicit DownloadExtractThread(const std::string &url, const std::string &localfolder); 18 | 19 | virtual ~DownloadExtractThread(); 20 | virtual void cancelDownload(); 21 | void setPostInstallScript(const std::string &path); 22 | void setLdapConfig(const std::string &config); 23 | 24 | protected: 25 | std::string _folder, _postInstallScript, _ldapConfig; 26 | std::thread *_extractThread; 27 | std::deque _queue; 28 | static const int MAX_QUEUE_SIZE; 29 | std::mutex _queueMutex; 30 | std::condition_variable _cv; 31 | 32 | std::string _popQueue(); 33 | void _pushQueue(const char *data, size_t len); 34 | void _cancelExtract(); 35 | virtual size_t _writeData(const char *buf, size_t len); 36 | virtual void _onDownloadSuccess(); 37 | virtual void _onDownloadError(const std::string &msg); 38 | virtual void extractRun(); 39 | 40 | ssize_t _on_read(struct archive *a, const void **buff); 41 | int _on_close(struct archive *a); 42 | 43 | static ssize_t _archive_read(struct archive *a, void *client_data, const void **buff); 44 | static int _archive_close(struct archive *a, void *client_data); 45 | }; 46 | 47 | #endif // DOWNLOADEXTRACTTHREAD_H 48 | -------------------------------------------------------------------------------- /src/downloadthread.cpp: -------------------------------------------------------------------------------- 1 | #include "downloadthread.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | using namespace std; 11 | 12 | string DownloadThread::_proxy; 13 | int DownloadThread::_curlCount = 0; 14 | 15 | DownloadThread::DownloadThread(const string &url, const string &localfilename) : 16 | _thread(NULL), _lastDlTotal(0), _lastDlNow(0), _url(url), _cancelled(false), _successful(false), 17 | _lastModified(0), _serverTime(0), _filename(localfilename), _file(NULL), 18 | _startOffset(0), _lastFailureTime(0) 19 | { 20 | if (!_curlCount) 21 | curl_global_init(CURL_GLOBAL_DEFAULT); 22 | _curlCount++; 23 | 24 | if (!localfilename.empty()) 25 | _file = new ofstream(localfilename, ios_base::out | ios_base::binary | ios_base::trunc); 26 | } 27 | 28 | DownloadThread::~DownloadThread() 29 | { 30 | if (_thread) 31 | { 32 | _thread->join(); 33 | delete _thread; 34 | } 35 | if (_file) 36 | delete _file; 37 | 38 | if (!--_curlCount) 39 | curl_global_cleanup(); 40 | } 41 | 42 | void DownloadThread::start() 43 | { 44 | _thread = new thread(&DownloadThread::run, this); 45 | } 46 | 47 | void DownloadThread::setProxy(const string &proxy) 48 | { 49 | _proxy = proxy; 50 | } 51 | 52 | string DownloadThread::proxy() 53 | { 54 | return _proxy; 55 | } 56 | 57 | void DownloadThread::setUserAgent(const string &ua) 58 | { 59 | _useragent = ua; 60 | } 61 | 62 | /* Curl write callback function, let it call the object oriented version */ 63 | size_t DownloadThread::_curl_write_callback(char *ptr, size_t size, size_t nmemb, void *userdata) 64 | { 65 | return static_cast(userdata)->_writeData(ptr, size * nmemb); 66 | } 67 | 68 | int DownloadThread::_curl_xferinfo_callback(void *userdata, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) 69 | { 70 | return (static_cast(userdata)->_progress(dltotal, dlnow, ultotal, ulnow) == false); 71 | } 72 | 73 | size_t DownloadThread::_curl_header_callback( void *ptr, size_t size, size_t nmemb, void *userdata) 74 | { 75 | int len = size*nmemb; 76 | string headerstr((char *) ptr, len); 77 | static_cast(userdata)->_header(headerstr); 78 | 79 | return len; 80 | } 81 | 82 | 83 | void DownloadThread::run() 84 | { 85 | char errorBuf[CURL_ERROR_SIZE] = {0}; 86 | _c = curl_easy_init(); 87 | curl_easy_setopt(_c, CURLOPT_NOSIGNAL, 1); 88 | curl_easy_setopt(_c, CURLOPT_WRITEFUNCTION, &DownloadThread::_curl_write_callback); 89 | curl_easy_setopt(_c, CURLOPT_WRITEDATA, this); 90 | curl_easy_setopt(_c, CURLOPT_XFERINFOFUNCTION, &DownloadThread::_curl_xferinfo_callback); 91 | curl_easy_setopt(_c, CURLOPT_PROGRESSDATA, this); 92 | curl_easy_setopt(_c, CURLOPT_NOPROGRESS, 0); 93 | curl_easy_setopt(_c, CURLOPT_URL, _url.c_str()); 94 | curl_easy_setopt(_c, CURLOPT_FOLLOWLOCATION, 1); 95 | curl_easy_setopt(_c, CURLOPT_MAXREDIRS, 10); 96 | curl_easy_setopt(_c, CURLOPT_ERRORBUFFER, errorBuf); 97 | curl_easy_setopt(_c, CURLOPT_FAILONERROR, 1); 98 | curl_easy_setopt(_c, CURLOPT_HEADERFUNCTION, &DownloadThread::_curl_header_callback); 99 | curl_easy_setopt(_c, CURLOPT_HEADERDATA, this); 100 | 101 | if (!_useragent.empty()) 102 | curl_easy_setopt(_c, CURLOPT_USERAGENT, _useragent.c_str()); 103 | if (!_proxy.empty()) 104 | curl_easy_setopt(_c, CURLOPT_PROXY, _proxy.c_str()); 105 | if (!_file && !_cachefile.empty()) 106 | { 107 | struct stat st; 108 | 109 | if (::stat(_cachefile.c_str(), &st) == 0 && st.st_size > 0) 110 | { 111 | curl_easy_setopt(_c, CURLOPT_TIMECONDITION, CURL_TIMECOND_IFMODSINCE); 112 | curl_easy_setopt(_c, CURLOPT_TIMEVALUE, st.st_mtim); 113 | } 114 | } 115 | 116 | CURLcode ret = curl_easy_perform(_c); 117 | 118 | /* Deal with badly configured HTTP servers that terminate the connection quickly 119 | if connections stalls for some seconds while kernel commits buffers to slow SD card */ 120 | while (ret == CURLE_PARTIAL_FILE) 121 | { 122 | time_t t = time(NULL); 123 | 124 | /* If last failure happened less than 5 seconds ago, something else may 125 | be wrong. Sleep some time to prevent hammering server */ 126 | if (t - _lastFailureTime < 5) 127 | ::sleep(10); 128 | _lastFailureTime = t; 129 | 130 | _startOffset = _lastDlNow; 131 | curl_easy_setopt(_c, CURLOPT_RESUME_FROM_LARGE, _startOffset); 132 | 133 | ret = curl_easy_perform(_c); 134 | } 135 | 136 | if (_file) 137 | _file->close(); 138 | curl_easy_cleanup(_c); 139 | 140 | switch (ret) 141 | { 142 | case CURLE_OK: 143 | if (!_cachefile.empty()) 144 | { 145 | int code = 0; 146 | curl_easy_getinfo(_c, CURLINFO_RESPONSE_CODE, &code); 147 | 148 | if (code == 304) 149 | { 150 | ifstream i(_cachefile, ios_base::in | ios_base::binary); 151 | stringstream buffer; 152 | buffer << i.rdbuf(); 153 | _buf = buffer.str(); 154 | } 155 | else if (_lastModified) 156 | { 157 | ofstream f(_cachefile, ios_base::out | ios_base::trunc | ios_base::binary); 158 | f.write(_buf.c_str(), _buf.size()); 159 | f.close(); 160 | 161 | struct timeval tvp[2]; 162 | tvp[0].tv_sec = 0; 163 | tvp[0].tv_usec = 0; 164 | tvp[1].tv_sec = _lastModified; 165 | tvp[1].tv_usec = 0; 166 | utimes(_cachefile.c_str(), tvp); 167 | } 168 | } 169 | _successful = true; 170 | _onDownloadSuccess(); 171 | break; 172 | case CURLE_WRITE_ERROR: 173 | deleteDownloadedFile(); 174 | _onDownloadError("Error writing file to disk"); 175 | break; 176 | case CURLE_ABORTED_BY_CALLBACK: 177 | break; 178 | default: 179 | deleteDownloadedFile(); 180 | _onDownloadError(errorBuf); 181 | } 182 | } 183 | 184 | size_t DownloadThread::_writeData(const char *buf, size_t len) 185 | { 186 | if (_file) 187 | { 188 | _file->write(buf, len); 189 | return _file->fail() ? 0 : len; 190 | } 191 | else 192 | { 193 | _buf.append(buf, len); 194 | return len; 195 | } 196 | } 197 | 198 | bool DownloadThread::_progress(curl_off_t dltotal, curl_off_t dlnow, curl_off_t /*ultotal*/, curl_off_t /*ulnow*/) 199 | { 200 | _lastDlTotal = _startOffset + dltotal; 201 | _lastDlNow = _startOffset + dlnow; 202 | 203 | return !_cancelled; 204 | } 205 | 206 | void DownloadThread::_header(const string &header) 207 | { 208 | if (header.compare(0, 6, "Date: ") == 0) 209 | { 210 | _serverTime = curl_getdate(header.data()+6, NULL); 211 | } 212 | else if (header.compare(0, 15, "Last-Modified: ") == 0) 213 | { 214 | _lastModified = curl_getdate(header.data()+15, NULL); 215 | } 216 | } 217 | 218 | void DownloadThread::cancelDownload() 219 | { 220 | _cancelled = true; 221 | deleteDownloadedFile(); 222 | } 223 | 224 | string DownloadThread::data() 225 | { 226 | return _buf; 227 | } 228 | 229 | bool DownloadThread::successfull() 230 | { 231 | return _successful; 232 | } 233 | 234 | time_t DownloadThread::lastModified() 235 | { 236 | return _lastModified; 237 | } 238 | 239 | time_t DownloadThread::serverTime() 240 | { 241 | return _serverTime; 242 | } 243 | 244 | void DownloadThread::deleteDownloadedFile() 245 | { 246 | if (_file) 247 | { 248 | _file->close(); 249 | ::unlink(_filename.c_str()); 250 | } 251 | } 252 | 253 | void DownloadThread::setCacheFile(const string &filename) 254 | { 255 | _cachefile = filename; 256 | } 257 | 258 | uint64_t DownloadThread::dlNow() 259 | { 260 | return _lastDlNow; 261 | } 262 | 263 | uint64_t DownloadThread::dltotal() 264 | { 265 | return _lastDlTotal; 266 | } 267 | 268 | string DownloadThread::lastError() 269 | { 270 | lock_guard lock(_errorMutex); 271 | string err = _lastError; 272 | 273 | return err; 274 | } 275 | 276 | void DownloadThread::_onDownloadSuccess() 277 | { 278 | _emitSuccess(); 279 | } 280 | 281 | void DownloadThread::_onDownloadError(const std::string &msg) 282 | { 283 | _emitError(msg); 284 | } 285 | 286 | 287 | void DownloadThread::_emitSuccess() 288 | { 289 | _signalSucess.emit(); 290 | } 291 | 292 | void DownloadThread::_emitError(const std::string &msg) 293 | { 294 | lock_guard lock(_errorMutex); 295 | _lastError = msg; 296 | _signalError.emit(); 297 | } 298 | 299 | Glib::Dispatcher &DownloadThread::signalSuccess() 300 | { 301 | return _signalSucess; 302 | } 303 | 304 | Glib::Dispatcher &DownloadThread::signalError() 305 | { 306 | return _signalError; 307 | } 308 | -------------------------------------------------------------------------------- /src/downloadthread.h: -------------------------------------------------------------------------------- 1 | #ifndef DOWNLOADTHREAD_H 2 | #define DOWNLOADTHREAD_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | class DownloadThread 14 | { 15 | public: 16 | /* 17 | * Constructor 18 | * 19 | * - url: URL to download 20 | * - localfilename: File name to save downloaded file as. If empty, store data in memory buffer 21 | */ 22 | explicit DownloadThread(const std::string &url, const std::string &localfilename = ""); 23 | 24 | /* 25 | * Destructor 26 | * 27 | * Waits until download is complete 28 | * If this is not desired, call cancelDownload() first 29 | */ 30 | virtual ~DownloadThread(); 31 | 32 | /* 33 | * Cancel download 34 | * 35 | * Async function. Returns immedeately, but can take a second before download actually stops 36 | */ 37 | virtual void cancelDownload(); 38 | 39 | /* 40 | * Set proxy server. 41 | * Specify a string like this: user:pass@proxyserver:8080/ 42 | * Used globally, for all connections 43 | */ 44 | static void setProxy(const std::string &proxy); 45 | 46 | /* 47 | * Returns proxy server used 48 | */ 49 | static std::string proxy(); 50 | 51 | /* 52 | * Set user-agent header string 53 | */ 54 | void setUserAgent(const std::string &ua); 55 | 56 | /* 57 | * Returns true if download has been successful 58 | */ 59 | bool successfull(); 60 | 61 | /* 62 | * Returns the downloaded data if saved to memory buffer instead of file 63 | */ 64 | std::string data(); 65 | 66 | /* 67 | * Delete downloaded file 68 | */ 69 | void deleteDownloadedFile(); 70 | 71 | /* 72 | * Return last-modified date (if available) as unix timestamp 73 | * (seconds since 1970) 74 | */ 75 | time_t lastModified(); 76 | 77 | /* 78 | * Return current server time as unix timestamp 79 | */ 80 | time_t serverTime(); 81 | 82 | /* 83 | * Cache result in local file 84 | */ 85 | void setCacheFile(const std::string &filename); 86 | 87 | /* 88 | * Thread safe download progress query functions 89 | */ 90 | uint64_t dlNow(); 91 | uint64_t dltotal(); 92 | std::string lastError(); 93 | 94 | /* 95 | * Thread safe signals 96 | * Creator of the DownloadThread object must have a glib main loop 97 | */ 98 | Glib::Dispatcher &signalSuccess(); 99 | Glib::Dispatcher &signalError(); 100 | 101 | /* 102 | * Thread start function 103 | */ 104 | void start(); 105 | 106 | protected: 107 | virtual void run(); 108 | virtual void _onDownloadSuccess(); 109 | virtual void _onDownloadError(const std::string &msg); 110 | void _emitSuccess(); 111 | void _emitError(const std::string &msg); 112 | 113 | /* 114 | * libcurl callbacks 115 | */ 116 | virtual size_t _writeData(const char *buf, size_t len); 117 | bool _progress(curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow); 118 | void _header(const std::string &header); 119 | 120 | static size_t _curl_write_callback(char *ptr, size_t size, size_t nmemb, void *userdata); 121 | static int _curl_xferinfo_callback(void *userdata, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow); 122 | static size_t _curl_header_callback( void *ptr, size_t size, size_t nmemb, void *userdata); 123 | 124 | std::thread *_thread; 125 | CURL *_c; 126 | curl_off_t _startOffset; 127 | std::atomic _lastDlTotal, _lastDlNow; 128 | std::mutex _errorMutex; 129 | std::string _url, _useragent, _buf, _cachefile, _filename, _lastError; 130 | static std::string _proxy; 131 | static int _curlCount; 132 | bool _cancelled, _successful; 133 | time_t _lastModified, _serverTime, _lastFailureTime; 134 | std::ofstream *_file; 135 | Glib::Dispatcher _signalSucess; 136 | Glib::Dispatcher _signalError; 137 | }; 138 | 139 | #endif // DOWNLOADTHREAD_H 140 | -------------------------------------------------------------------------------- /src/edithostdialog.cpp: -------------------------------------------------------------------------------- 1 | #include "edithostdialog.h" 2 | #include "host.h" 3 | #include "distribution.h" 4 | #include 5 | 6 | using namespace std; 7 | 8 | EditHostDialog::EditHostDialog(PiServer *ps, const std::string &mac, Gtk::Window *parent) 9 | : _ps(ps), _mac(mac) 10 | { 11 | auto builder = Gtk::Builder::create_from_resource("/data/edithostdialog.glade"); 12 | builder->get_widget("edithostdialog", _dialog); 13 | builder->get_widget("maclabel", _maclabel); 14 | builder->get_widget("oscombo", _oscombo); 15 | builder->get_widget("descriptionentry", _descriptionentry); 16 | _distrostore = Glib::RefPtr::cast_dynamic( builder->get_object("distrostore") ); 17 | if (parent) 18 | _dialog->set_transient_for(*parent); 19 | 20 | Host *h = ps->getHost(mac); 21 | _maclabel->set_text(mac); 22 | _descriptionentry->set_text(h->description()); 23 | _distrostore->clear(); 24 | auto distros = _ps->getDistributions(); 25 | 26 | for (auto &kv : *distros) 27 | { 28 | auto row = _distrostore->append(); 29 | row->set_value(0, kv.second->name()); 30 | 31 | if (kv.second == h->distro()) 32 | { 33 | _oscombo->set_active(row); 34 | } 35 | } 36 | 37 | if (!h->distro() && !distros->empty()) 38 | _oscombo->set_active(0); 39 | 40 | _descriptionentry->grab_focus(); 41 | } 42 | 43 | EditHostDialog::~EditHostDialog() 44 | { 45 | delete _dialog; 46 | } 47 | 48 | bool EditHostDialog::exec() 49 | { 50 | if (_dialog->run() == Gtk::RESPONSE_OK) 51 | { 52 | string distroname; 53 | string description = _descriptionentry->get_text(); 54 | _oscombo->get_active()->get_value(0, distroname); 55 | Distribution *distro = _ps->getDistributions()->at(distroname); 56 | 57 | Host *h = _ps->getHost(_mac); 58 | if (!h) 59 | return false; /* Host doesn't exist anymore */ 60 | 61 | h->setDescription(description); 62 | h->setDistro(distro); 63 | _ps->updateHost(h); 64 | 65 | return true; 66 | } 67 | 68 | return false; 69 | } 70 | -------------------------------------------------------------------------------- /src/edithostdialog.h: -------------------------------------------------------------------------------- 1 | #ifndef EDITHOSTDIALOG_H 2 | #define EDITHOSTDIALOG_H 3 | 4 | #include "piserver.h" 5 | #include 6 | #include 7 | 8 | class EditHostDialog 9 | { 10 | public: 11 | EditHostDialog(PiServer *ps, const std::string &mac, Gtk::Window *parent = NULL); 12 | virtual ~EditHostDialog(); 13 | bool exec(); 14 | 15 | protected: 16 | PiServer *_ps; 17 | std::string _mac; 18 | Gtk::Dialog *_dialog; 19 | Gtk::Label *_maclabel; 20 | Gtk::ComboBox *_oscombo; 21 | Gtk::Entry *_descriptionentry; 22 | Glib::RefPtr _distrostore; 23 | 24 | }; 25 | 26 | #endif // EDITHOSTDIALOG_H 27 | -------------------------------------------------------------------------------- /src/edituserdialog.cpp: -------------------------------------------------------------------------------- 1 | #include "edituserdialog.h" 2 | 3 | using namespace std; 4 | 5 | EditUserDialog::EditUserDialog(PiServer *ps, const std::string &dn, const std::string &user, const std::string &desc, Gtk::Window *parent) 6 | : _ps(ps), _parentWindow(parent), _dn(dn), _user(user), _oldDesc(desc) 7 | { 8 | Gtk::Label *userlabel; 9 | auto builder = Gtk::Builder::create_from_resource("/data/edituserdialog.glade"); 10 | builder->get_widget("edituserdialog", _dialog); 11 | builder->get_widget("okbutton", _okButton); 12 | builder->get_widget("descentry", _descEntry); 13 | builder->get_widget("passentry", _passEntry); 14 | builder->get_widget("pass2entry", _pass2Entry); 15 | builder->get_widget("showpasscheck", _showPassCheck); 16 | builder->get_widget("userlabel", userlabel); 17 | userlabel->set_text(user); 18 | _descEntry->set_text(desc); 19 | _passEntry->signal_changed().connect(sigc::mem_fun(this, &EditUserDialog::setOkButton)); 20 | _pass2Entry->signal_changed().connect(sigc::mem_fun(this, &EditUserDialog::setOkButton)); 21 | _showPassCheck->signal_toggled().connect(sigc::mem_fun(this, &EditUserDialog::on_showPassToggled)); 22 | 23 | if (parent) 24 | _dialog->set_transient_for(*parent); 25 | } 26 | 27 | EditUserDialog::~EditUserDialog() 28 | { 29 | delete _dialog; 30 | } 31 | 32 | bool EditUserDialog::exec() 33 | { 34 | if (_dialog->run() == Gtk::RESPONSE_OK) 35 | { 36 | try 37 | { 38 | multimap changes; 39 | string desc = _descEntry->get_text(); 40 | 41 | if (desc != _oldDesc) 42 | changes.insert( make_pair("description", desc) ); 43 | 44 | if (!_passEntry->get_text().empty()) 45 | changes.insert( make_pair("password", _passEntry->get_text()) ); 46 | 47 | if (!changes.empty()) 48 | _ps->updateUser(_dn, changes); 49 | 50 | return true; 51 | } 52 | catch (std::exception &e) 53 | { 54 | Gtk::MessageDialog ed(e.what()); 55 | ed.set_transient_for(*_parentWindow); 56 | ed.run(); 57 | } 58 | } 59 | 60 | return false; 61 | } 62 | 63 | void EditUserDialog::setOkButton() 64 | { 65 | _okButton->set_sensitive(_passEntry->get_text() == _pass2Entry->get_text()); 66 | } 67 | 68 | void EditUserDialog::on_showPassToggled() 69 | { 70 | bool showPass = _showPassCheck->get_active(); 71 | 72 | _passEntry->set_visibility(showPass); 73 | _pass2Entry->set_visibility(showPass); 74 | } 75 | 76 | std::string EditUserDialog::description() 77 | { 78 | return _descEntry->get_text(); 79 | } 80 | -------------------------------------------------------------------------------- /src/edituserdialog.h: -------------------------------------------------------------------------------- 1 | #ifndef EDITUSERDIALOG_H 2 | #define EDITUSERDIALOG_H 3 | 4 | #include "piserver.h" 5 | #include 6 | 7 | class EditUserDialog 8 | { 9 | public: 10 | EditUserDialog(PiServer *ps, const std::string &dn, const std::string &user, const std::string &desc, Gtk::Window *parent = NULL); 11 | virtual ~EditUserDialog(); 12 | bool exec(); 13 | std::string description(); 14 | 15 | protected: 16 | PiServer *_ps; 17 | Gtk::Window *_parentWindow; 18 | std::string _dn, _user, _oldDesc; 19 | Gtk::Dialog *_dialog; 20 | Gtk::Entry *_passEntry, *_pass2Entry, *_descEntry; 21 | Gtk::Button *_okButton; 22 | Gtk::CheckButton *_showPassCheck; 23 | 24 | void setOkButton(); 25 | void on_showPassToggled(); 26 | }; 27 | 28 | #endif // EDITUSERDIALOG_H 29 | -------------------------------------------------------------------------------- /src/exportdistrodialog.cpp: -------------------------------------------------------------------------------- 1 | #include "exportdistrodialog.h" 2 | #include "distribution.h" 3 | #include "piserver.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | using namespace std; 10 | 11 | ExportDistroDialog::ExportDistroDialog(PiServer *ps, Distribution *distro, Gtk::Window *parent) 12 | : _ps(ps), _distro(distro), _parent(parent) 13 | { 14 | } 15 | 16 | ExportDistroDialog::~ExportDistroDialog() 17 | { 18 | } 19 | 20 | bool ExportDistroDialog::exec() 21 | { 22 | string filename, dir; 23 | Glib::Date date; 24 | 25 | Gtk::FileChooserDialog fd(*_parent, _("Please choose a file name"), Gtk::FILE_CHOOSER_ACTION_SAVE); 26 | fd.set_transient_for(*_parent); 27 | auto filter = Gtk::FileFilter::create(); 28 | filter->set_name(".tar.xz files"); 29 | filter->add_pattern("*.tar.xz"); 30 | fd.add_filter(filter); 31 | date.set_time_current(); 32 | fd.set_current_name(_distro->name()+" "+date.format_string("%Y%m%d")+".tar.xz"); 33 | fd.set_do_overwrite_confirmation(true); 34 | fd.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL); 35 | fd.add_button(Gtk::Stock::SAVE, Gtk::RESPONSE_OK); 36 | 37 | if (fd.run() != Gtk::RESPONSE_OK) 38 | return false; 39 | 40 | fd.hide(); 41 | filename = fd.get_filename(); 42 | dir = _distro->distroPath(); 43 | if (!Glib::str_has_suffix(filename, ".tar.xz")) 44 | filename += ".tar.xz"; 45 | 46 | try 47 | { 48 | const gchar *cmd[] = {"bsdtar", "-C", dir.c_str(), "--numeric-owner", "--one-file-system", "-cJf", filename.c_str(), ".", NULL}; 49 | GError *error = NULL; 50 | 51 | if (!g_spawn_async(NULL, (gchar **) cmd, NULL, 52 | (GSpawnFlags) (G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH), NULL, NULL, &_pid, &error)) 53 | throw runtime_error(error->message); 54 | 55 | _progressDialog = new Gtk::MessageDialog(*_parent, _("Creating archive (can take 10+ minutes)"), 56 | false, Gtk::MESSAGE_OTHER, Gtk::BUTTONS_NONE, false); 57 | sigc::connection cwatch = Glib::signal_child_watch().connect(sigc::mem_fun(this, &ExportDistroDialog::onTarFinished), _pid); 58 | _parent->get_window()->set_cursor(Gdk::Cursor::create(Gdk::WATCH)); 59 | int result = _progressDialog->run(); 60 | _parent->get_window()->set_cursor(); 61 | cwatch.disconnect(); 62 | g_spawn_close_pid(_pid); 63 | delete _progressDialog; 64 | _progressDialog = NULL; 65 | 66 | if (result != 0) 67 | { 68 | string msg = Glib::ustring::compose(_("Error occured while creating archive (statuscode=%1)"), result); 69 | 70 | if (result == Gtk::RESPONSE_DELETE_EVENT) 71 | { 72 | ::kill(_pid, SIGTERM); 73 | msg = _("Creating archive cancelled"); 74 | } 75 | 76 | Gtk::MessageDialog d(msg); 77 | d.set_transient_for(*_parent); 78 | d.run(); 79 | 80 | ::unlink(filename.c_str() ); 81 | } 82 | else 83 | { 84 | struct passwd *userinfo; 85 | const char *user = getenv("SUDO_USER"); 86 | if (!user) 87 | user = getenv("USER"); 88 | 89 | if (user) 90 | { 91 | userinfo = ::getpwnam(user); 92 | if (userinfo) 93 | { 94 | if (::chown(filename.c_str(), userinfo->pw_uid, userinfo->pw_gid) == -1) { } 95 | } 96 | } 97 | 98 | return true; 99 | } 100 | } 101 | catch (const runtime_error &r) 102 | { 103 | Gtk::MessageDialog d(r.what()); 104 | d.set_transient_for(*_parent); 105 | d.run(); 106 | } 107 | 108 | return false; 109 | } 110 | 111 | void ExportDistroDialog::onTarFinished(GPid pid, int status) 112 | { 113 | if (pid == _pid && _progressDialog) 114 | _progressDialog->response(status); 115 | } 116 | -------------------------------------------------------------------------------- /src/exportdistrodialog.h: -------------------------------------------------------------------------------- 1 | #ifndef EXPORTDISTRODIALOG_H 2 | #define EXPORTDISTRODIALOG_H 3 | 4 | #include 5 | #include 6 | 7 | class PiServer; 8 | class Distribution; 9 | 10 | class ExportDistroDialog 11 | { 12 | public: 13 | ExportDistroDialog(PiServer *ps, Distribution *olddistro, Gtk::Window *parent); 14 | virtual ~ExportDistroDialog(); 15 | bool exec(); 16 | 17 | protected: 18 | PiServer *_ps; 19 | Distribution *_distro; 20 | Gtk::Dialog *_progressDialog; 21 | GPid _pid; 22 | Gtk::Window *_parent; 23 | 24 | void onTarFinished(GPid pid, int status); 25 | }; 26 | 27 | #endif // EXPORTDISTRODIALOG_H 28 | -------------------------------------------------------------------------------- /src/host.cpp: -------------------------------------------------------------------------------- 1 | #include "host.h" 2 | #include "piserver.h" 3 | #include 4 | 5 | Host::Host(const std::string &m, const std::string &desc, Distribution *distr) 6 | : _mac(m), _description(desc), _distro(distr) 7 | { 8 | } 9 | 10 | void Host::setDescription(const std::string &descr) 11 | { 12 | _description = descr; 13 | } 14 | 15 | void Host::setDistro(Distribution *distro) 16 | { 17 | _distro = distro; 18 | } 19 | 20 | std::string Host::tftpPath() 21 | { 22 | std::string dashedMac = _mac; 23 | replace(dashedMac.begin(), dashedMac.end(), ':', '-'); 24 | 25 | return std::string(PISERVER_TFTPROOT)+"/"+dashedMac; 26 | } 27 | -------------------------------------------------------------------------------- /src/host.h: -------------------------------------------------------------------------------- 1 | #ifndef HOST_H 2 | #define HOST_H 3 | 4 | #include 5 | 6 | class Distribution; 7 | 8 | class Host 9 | { 10 | public: 11 | Host(const std::string &m, const std::string &desc, Distribution *distr); 12 | void setDescription(const std::string &descr); 13 | void setDistro(Distribution *distro); 14 | std::string tftpPath(); 15 | 16 | inline const std::string &mac() 17 | { 18 | return _mac; 19 | } 20 | 21 | inline const std::string &description() 22 | { 23 | return _description; 24 | } 25 | 26 | inline Distribution *distro() 27 | { 28 | return _distro; 29 | } 30 | 31 | protected: 32 | std::string _mac, _description; 33 | Distribution *_distro; 34 | 35 | }; 36 | 37 | #endif // HOST_H 38 | -------------------------------------------------------------------------------- /src/importosthread.cpp: -------------------------------------------------------------------------------- 1 | #include "importosthread.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "config.h" 8 | 9 | ImportOSThread::ImportOSThread(const std::string &device, const std::string &localfolder) 10 | : DownloadExtractThread("", localfolder), _device(device) 11 | { 12 | _lastDlTotal = 100; 13 | } 14 | 15 | void ImportOSThread::run() 16 | { 17 | /* Step 1: mounting SD card */ 18 | std::string bootdev, rootdev; 19 | std::string mntdir = PISERVER_DATADIR "/mnt"; 20 | std::string bootmntdir = mntdir+"/boot"; 21 | 22 | if ( isdigit(_device.back()) ) 23 | { 24 | bootdev = _device+"p1"; 25 | rootdev = _device+"p2"; 26 | } 27 | else 28 | { 29 | bootdev = _device+"1"; 30 | rootdev = _device+"2"; 31 | } 32 | 33 | if (!std::filesystem::exists(bootdev)) 34 | { 35 | _emitError(std::string("Boot (FAT) partition ")+bootdev+" does not exist"); 36 | return; 37 | } 38 | if (!std::filesystem::exists(rootdev)) 39 | { 40 | _emitError(std::string("Root (ext4) partition ")+rootdev+" does not exist"); 41 | return; 42 | } 43 | 44 | if (::mkdir(mntdir.c_str(), 0755) == -1) { /* Ignore errors. Folder may already exist */ } 45 | 46 | if (::mount(rootdev.c_str(), mntdir.c_str(), "ext4", MS_RDONLY, "") != 0 47 | && ::mount(rootdev.c_str(), mntdir.c_str(), "ext4", 0, "") != 0) 48 | { 49 | _emitError(std::string("Error mounting ")+rootdev+" as ext4: "+strerror(errno) ); 50 | return; 51 | } 52 | 53 | if (!std::filesystem::exists(bootmntdir)) 54 | { 55 | ::umount(mntdir.c_str() ); 56 | _emitError("Error: no /boot directory inside ext4 file system"); 57 | return; 58 | } 59 | 60 | if (::mount(bootdev.c_str(), bootmntdir.c_str(), "vfat", MS_RDONLY, "") != 0 61 | && ::mount(bootdev.c_str(), bootmntdir.c_str(), "vfat", 0, "") != 0) 62 | { 63 | ::umount(mntdir.c_str() ); 64 | _emitError(std::string("Error mounting ")+bootdev+" as vfat: "+strerror(errno) ); 65 | return; 66 | } 67 | 68 | /* Step 2: copying files */ 69 | _lastDlNow = 25; 70 | 71 | std::string cmd = std::string("cp -a ")+mntdir+"/* "+_folder; 72 | int retcode = ::system(cmd.c_str() ); 73 | ::umount(bootmntdir.c_str() ); 74 | ::umount(mntdir.c_str() ); 75 | 76 | if (retcode != 0) 77 | { 78 | _emitError("Error copying files from SD card"); 79 | return; 80 | } 81 | 82 | /* Step 3: run image conversion script */ 83 | _lastDlNow = 50; 84 | cmd = std::string(PISERVER_CONVERTSCRIPT)+" "+_folder; 85 | if (::system(cmd.c_str() ) != 0) 86 | { 87 | _emitError("Error running convert script"); 88 | return; 89 | } 90 | 91 | /* Step 4: normal post-install script */ 92 | _lastDlNow = 75; 93 | 94 | if (!_postInstallScript.empty()) 95 | { 96 | ::setenv("DISTROROOT", _folder.c_str(), 1); 97 | if (!_ldapConfig.empty()) 98 | ::setenv("EXTERNAL_LDAP_CONFIG", _ldapConfig.c_str(), 1); 99 | if (::system(_postInstallScript.c_str()) != 0) 100 | { 101 | _emitError("Error running post-installation script"); 102 | } 103 | else 104 | { 105 | _emitSuccess(); 106 | } 107 | ::unsetenv("DISTROROOT"); 108 | if (!_ldapConfig.empty()) 109 | ::unsetenv("EXTERNAL_LDAP_CONFIG"); 110 | } 111 | else 112 | { 113 | _emitSuccess(); 114 | } 115 | 116 | _lastDlNow = 100; 117 | } 118 | -------------------------------------------------------------------------------- /src/importosthread.h: -------------------------------------------------------------------------------- 1 | #ifndef IMPORTOSTHREAD_H 2 | #define IMPORTOSTHREAD_H 3 | 4 | #include "downloadextractthread.h" 5 | 6 | class ImportOSThread : public DownloadExtractThread 7 | { 8 | public: 9 | ImportOSThread(const std::string &device, const std::string &localfolder); 10 | 11 | protected: 12 | std::string _device; 13 | 14 | virtual void run(); 15 | }; 16 | 17 | #endif // IMPORTOSTHREAD_H 18 | -------------------------------------------------------------------------------- /src/installwizard.h: -------------------------------------------------------------------------------- 1 | #ifndef INSTALLWIZARD_H 2 | #define INSTALLWIZARD_H 3 | 4 | #include "piserver.h" 5 | #include "abstractaddhost.h" 6 | #include "abstractadduser.h" 7 | #include "abstractadddistro.h" 8 | #include "dependenciesinstallthread.h" 9 | #include 10 | 11 | class ActiveDiscovery; 12 | 13 | class InstallWizard : public AbstractAddHost, public AbstractAddUser, public AbstractAddDistro 14 | { 15 | public: 16 | InstallWizard(Glib::RefPtr app, PiServer *ps); 17 | virtual ~InstallWizard(); 18 | void exec(); 19 | 20 | protected: 21 | Glib::RefPtr _app; 22 | PiServer *_ps; 23 | Gtk::Assistant *_assistant; 24 | Gtk::ComboBox *_ifacecombo; 25 | Glib::RefPtr _ethstore, _repostore; 26 | Gtk::Widget *_addHostPage, *_addUserPage, *_addDistroPage, *_progressPage, *_authSelectPage; 27 | DependenciesInstallThread *_dependsThread; 28 | Gtk::RadioButton *_localLdapRadio, *_extLdapRadio, *_restrictByGroupRadio; 29 | Gtk::Frame *_extLdapFrame; 30 | Gtk::ComboBox *_ldapTypeBox; 31 | Gtk::Entry *_ldapServerEntry, *_domainEntry, *_bindUserEntry, *_bindPassEntry; 32 | ActiveDiscovery *_activeDiscovery; 33 | int _prevPage; 34 | 35 | virtual void setAddHostOkButton(); 36 | virtual void setAddUserOkButton(); 37 | virtual void setAddDistroOkButton(); 38 | void onClose(); 39 | void onPagePrepare(Gtk::Widget *newPage); 40 | void onDependsInstallComplete(); 41 | void onDependsInstallFailed(); 42 | virtual void onInstallationSuccess(); 43 | virtual void onInstallationFailed(const std::string &error); 44 | void authSelectionRadioChange(); 45 | void setAuthSelectionOkButton(); 46 | void adDiscovered(const std::string &server, const std::string &domain); 47 | void adDiscoveryFailed(); 48 | 49 | std::string _randomStr(int len); 50 | }; 51 | 52 | #endif // INSTALLWIZARD_H 53 | -------------------------------------------------------------------------------- /src/ldapsettingsdialog.cpp: -------------------------------------------------------------------------------- 1 | #include "ldapsettingsdialog.h" 2 | #include "piserver.h" 3 | #include 4 | #include 5 | 6 | using namespace std; 7 | 8 | LdapSettingsDialog::LdapSettingsDialog(PiServer *ps, Gtk::Window *parent) 9 | : _ps(ps) 10 | { 11 | auto builder = Gtk::Builder::create_from_resource("/data/ldapsettingsdialog.glade"); 12 | builder->get_widget("dialog", _dialog); 13 | builder->get_widget("dntree", _dntree); 14 | builder->get_widget("grouptree", _grouptree); 15 | _dnstore = Glib::RefPtr::cast_dynamic( builder->get_object("dnstore") ); 16 | _groupstore = Glib::RefPtr::cast_dynamic( builder->get_object("groupstore") ); 17 | 18 | string currentDN = _ps->getSetting("ldapDN"); 19 | string currentGroup = _ps->getSetting("ldapGroup"); 20 | set DNs = _ps->getPotentialBaseDNs(); 21 | set groups = _ps->getLdapGroups(); 22 | 23 | for (string dn : DNs) 24 | { 25 | char *sortname = ldap_dn2dcedn(dn.c_str()); 26 | string sortstring = sortname; 27 | ldap_memfree(sortname); 28 | auto row = _dnstore->append(); 29 | row->set_value(0, dn); 30 | row->set_value(1, sortstring); 31 | if ( !strcasecmp(dn.c_str(), currentDN.c_str()) ) 32 | _dntree->get_selection()->select(row); 33 | } 34 | _dnstore->set_sort_column(1, Gtk::SORT_ASCENDING); 35 | 36 | for (string group : groups) 37 | { 38 | auto row = _groupstore->append(); 39 | row->set_value(0, group); 40 | if (group == currentGroup) 41 | _grouptree->get_selection()->select(row); 42 | } 43 | 44 | /* If nothing is selected already, select first row */ 45 | if (!_dntree->get_selection()->get_selected()) 46 | { 47 | auto iter = _dnstore->children().begin(); 48 | if (iter) 49 | { 50 | _grouptree->get_selection()->select(iter); 51 | } 52 | } 53 | if (!_grouptree->get_selection()->get_selected()) 54 | { 55 | auto iter = _groupstore->children().begin(); 56 | if (iter) 57 | { 58 | _grouptree->get_selection()->select(iter); 59 | } 60 | } 61 | 62 | if (parent) 63 | _dialog->set_transient_for(*parent); 64 | } 65 | 66 | LdapSettingsDialog::~LdapSettingsDialog() 67 | { 68 | delete _dialog; 69 | } 70 | 71 | bool LdapSettingsDialog::exec() 72 | { 73 | if (_dialog->run() != Gtk::RESPONSE_OK) 74 | return false; 75 | 76 | string dn, group, ldapExtraConfig, ldapServerType; 77 | auto dniter = _dntree->get_selection()->get_selected(); 78 | auto groupiter = _grouptree->get_selection()->get_selected(); 79 | 80 | if (!dniter || !groupiter) 81 | return false; 82 | 83 | dniter->get_value(0, dn); 84 | groupiter->get_value(0, group); 85 | ldapServerType = _ps->getSetting("ldapServerType"); 86 | ldapExtraConfig = _ps->getSetting("ldapExtraConfig"); 87 | regex filterRegex("(filter passwd [^\n]+\n)"); 88 | 89 | /* Remove existing filter setting if any */ 90 | ldapExtraConfig = regex_replace(ldapExtraConfig, filterRegex, ""); 91 | 92 | if (group == "[all groups allowed]") 93 | group = ""; 94 | 95 | ldapExtraConfig += "filter passwd "+_ps->getLdapFilter(group)+"\n"; 96 | _ps->setSetting("ldapExtraConfig", ldapExtraConfig); 97 | _ps->setSetting("ldapGroup", group); 98 | _ps->setSetting("ldapDN", dn); 99 | 100 | return true; 101 | } 102 | -------------------------------------------------------------------------------- /src/ldapsettingsdialog.h: -------------------------------------------------------------------------------- 1 | #ifndef LDAPSETTINGSDIALOG_H 2 | #define LDAPSETTINGSDIALOG_H 3 | 4 | #include 5 | #include 6 | 7 | class PiServer; 8 | 9 | class LdapSettingsDialog 10 | { 11 | public: 12 | LdapSettingsDialog(PiServer *ps, Gtk::Window *parent); 13 | virtual ~LdapSettingsDialog(); 14 | bool exec(); 15 | 16 | protected: 17 | PiServer *_ps; 18 | Gtk::Dialog *_dialog; 19 | Gtk::TreeView *_dntree, *_grouptree; 20 | Glib::RefPtr _dnstore, _groupstore; 21 | }; 22 | 23 | #endif // LDAPSETTINGSDIALOG_H 24 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "mainwindow.h" 5 | #include "installwizard.h" 6 | #include "piserver.h" 7 | 8 | using namespace std; 9 | 10 | int main(int argc, char *argv[]) 11 | { 12 | PiServer ps; 13 | 14 | setlocale (LC_ALL, ""); 15 | bindtextdomain ("piserver", "/usr/share/locale"); 16 | bind_textdomain_codeset ("piserver", "UTF-8"); 17 | textdomain ("piserver"); 18 | 19 | if (argc == 2 && strcmp(argv[1], "--update-ip") == 0 ) 20 | { 21 | ps.updateIP(); 22 | return 0; 23 | } 24 | 25 | auto app = Gtk::Application::create(argc, argv, "org.raspberrypi.piserver"); 26 | 27 | if (::getuid() != 0) 28 | { 29 | Gtk::MessageDialog d(_("This program must be run as root. Try starting it with sudo.")); 30 | d.run(); 31 | return 1; 32 | } 33 | 34 | if (::access(PISERVER_DATADIR, R_OK | W_OK | X_OK) == -1 35 | || ::access(PISERVER_TFTPROOT, R_OK | W_OK | X_OK) == -1 36 | || ::access(PISERVER_DISTROROOT, R_OK | W_OK | X_OK) == -1) 37 | { 38 | Gtk::MessageDialog d(_("Sanity check failed. Data directories do not exist or have " 39 | "incorrect permissions. Please reinstall the 'piserver' package.")); 40 | d.run(); 41 | return 1; 42 | } 43 | 44 | if (!ps.getSetting("installed", false)) 45 | { 46 | /* Warn if using overlayfs, as Debian currently uses Linux 4.9.x 47 | * that does not support exporting overlayfs through NFS 48 | * Do not make warning fatal, as this may change in the future 49 | */ 50 | std::ifstream i("/proc/mounts", ios_base::in); 51 | std::stringstream buf; 52 | buf << i.rdbuf(); 53 | 54 | if (buf.str().find("overlay / ") != string::npos) 55 | { 56 | Gtk::MessageDialog d(_("Warning: you seem to be running Debian with persistence enabled, " 57 | "which does not work properly with piserver. Please 'install' Debian to " 58 | "hard disk first.")); 59 | d.run(); 60 | } 61 | 62 | InstallWizard w(app, &ps); 63 | w.exec(); 64 | /* For some reason gtk doesn't allow the main loop to be run a second time. 65 | create fresh Application object for use by MainWindow */ 66 | app = Gtk::Application::create(argc, argv, "org.raspberrypi.piserver"); 67 | } 68 | 69 | if (ps.getSetting("installed", false)) 70 | { 71 | MainWindow win(app, &ps); 72 | win.exec(); 73 | } 74 | 75 | return 0; 76 | } 77 | 78 | -------------------------------------------------------------------------------- /src/mainwindow.h: -------------------------------------------------------------------------------- 1 | #ifndef MAINWINDOW_H 2 | #define MAINWINDOW_H 3 | 4 | #include 5 | #include "piserver.h" 6 | 7 | class DownloadThread; 8 | 9 | class MainWindow 10 | { 11 | public: 12 | MainWindow(Glib::RefPtr app, PiServer *ps); 13 | virtual ~MainWindow(); 14 | void exec(); 15 | 16 | void _reloadDistro(); 17 | void _reloadHosts(); 18 | void _reloadUsers(); 19 | void _reloadFolders(); 20 | 21 | protected: 22 | Glib::RefPtr _app; 23 | Gtk::Window *_window; 24 | Gtk::Notebook *_notebook; 25 | Gtk::TreeView *_distrotree, *_usertree, *_hosttree, *_folderstree; 26 | Gtk::ToolButton *_edituserbutton, *_deluserbutton, *_edithostbutton, *_delhostbutton, 27 | *_upgradeosbutton, *_cloneosbutton, *_exportosbutton, *_delosbutton, *_shellbutton, *_delfolderbutton, *_openfolderbutton; 28 | Gtk::SearchEntry *_usersearchentry; 29 | Gtk::Entry *_startipentry, *_endipentry, *_netmaskentry, *_gatewayentry; 30 | Gtk::Button *_savesettingsbutton; 31 | Gtk::ComboBox *_ifacecombo; 32 | Gtk::RadioButton *_proxydhcpradio, *_standaloneradio; 33 | Gtk::Frame *_dhcpserverframe; 34 | Gtk::Label *_softwarelabel; 35 | Glib::RefPtr _distrostore, _hoststore, _userstore, _ethstore, _folderstore; 36 | DownloadThread *_dt; 37 | PiServer *_ps; 38 | std::string _cachedDistroInfo; 39 | 40 | bool _validIP(const std::string &s); 41 | void _savesettings(); 42 | 43 | /* Slots */ 44 | void on_adduser_clicked(); 45 | void on_importusers_clicked(); 46 | void on_edituser_clicked(); 47 | void on_deluser_clicked(); 48 | void on_addhost_clicked(); 49 | void on_edithost_clicked(); 50 | void on_delhost_clicked(); 51 | void on_addos_clicked(); 52 | void on_upgradeos_clicked(); 53 | void on_delos_clicked(); 54 | void on_cloneos_clicked(); 55 | void on_exportos_clicked(); 56 | void on_shell_clicked(); 57 | void on_hosttree_activated(const Gtk::TreeModel::Path &path, Gtk::TreeViewColumn *col); 58 | void on_distrotree_activated(const Gtk::TreeModel::Path &path, Gtk::TreeViewColumn *col); 59 | void on_usertree_activated(const Gtk::TreeModel::Path &path, Gtk::TreeViewColumn *col); 60 | void on_foldertree_activated(const Gtk::TreeModel::Path &path, Gtk::TreeViewColumn *col); 61 | void onDownloadSuccessful(); 62 | void onDownloadFailed(); 63 | void onOtherDhcpServerDetected(const std::string &ip); 64 | void on_popup_clicked(); 65 | bool on_popup_button(GdkEventButton*,Gtk::Widget *sender); 66 | void _setSettingsSensitive(); 67 | void on_savesettings_clicked(); 68 | void on_addfolder_clicked(); 69 | void on_delfolder_clicked(); 70 | void on_openfolder_clicked(); 71 | }; 72 | 73 | #endif // MAINWINDOW_H 74 | -------------------------------------------------------------------------------- /src/piserver.h: -------------------------------------------------------------------------------- 1 | #ifndef PISERVER_H 2 | #define PISERVER_H 3 | 4 | #include "config.h" 5 | #include "user.h" 6 | #include "json/json.hpp" 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #ifdef LOCALEDIR 13 | #include 14 | #define _(str) gettext(str) 15 | #else 16 | #define _(str) str 17 | #endif 18 | 19 | 20 | class Host; 21 | class Distribution; 22 | struct FTW; 23 | struct stat; 24 | 25 | class PiServer 26 | { 27 | public: 28 | PiServer(); 29 | virtual ~PiServer(); 30 | 31 | /* User management functions */ 32 | void addUser(const std::string &name, const std::string &password, bool forcePasswordChange = false, int gid = PISERVER_GID); 33 | void addGroup(const std::string &name, const std::string &description, int gid = -1); 34 | void deleteGroup(const std::string &name); 35 | void deleteUser(const std::string &dn, const std::string &name); 36 | void updateUser(const std::string &dn, std::multimap &attr); 37 | bool isUsernameValid(const std::string &name); 38 | bool isUsernameAvailable(const std::string &name); 39 | bool isUsernameAvailableLocally(const std::string &name); 40 | std::map searchUsernames(const std::string &query = ""); 41 | 42 | /* Host management functions */ 43 | void addHosts(std::set macs, Distribution *d, const std::string &description = ""); 44 | void addHost(Host *h); 45 | void updateHost(Host *h, bool saveConfigs = true); 46 | void deleteHost(Host *h); 47 | Host *getHost(const std::string &mac); 48 | std::map *getHosts(); 49 | 50 | /* OS management functions */ 51 | std::map *getDistributions(); 52 | void deleteDistribution(const std::string &name); 53 | void addDistribution(Distribution *distro); 54 | void startChrootTerminal(const std::string &distribution); 55 | 56 | std::string currentIP(const std::string &iface = ""); 57 | std::map getInterfaces(); 58 | void updateIP(); 59 | 60 | void setSetting(const std::string &key, const std::string &value); 61 | void setSetting(const std::string &key, int value); 62 | std::string getSetting(const std::string &key, const std::string &defaultValue = ""); 63 | int getSetting(const std::string &key, int defaultValue); 64 | void unsetSetting(const std::string &key); 65 | void saveSettings(); 66 | void regenDnsmasqConf(bool restartDnsmasqIfChanged = true); 67 | void addToExports(const std::string &line); 68 | double availableDiskSpace(const std::string &path = PISERVER_DISTROROOT); 69 | bool hasArmCpu(); 70 | bool externalServer(); 71 | std::string getDomainSidFromLdap(const std::string &server, const std::string &servertype, const std::string &basedn, const std::string &bindUser, const std::string &bindPass); 72 | std::set getPotentialBaseDNs(); 73 | std::set getLdapGroups(); 74 | std::string getLdapFilter(const std::string &forGroup); 75 | 76 | protected: 77 | std::map _hosts; 78 | std::map _distro; 79 | LDAP *_ldap; 80 | nlohmann::json _settings; 81 | 82 | void _saveHosts(); 83 | void _saveDistributions(); 84 | void _patchDistributions(); 85 | void _restartService(const char *name); 86 | void _connectToLDAP(); 87 | std::string _ldapEscape(const std::string &input); 88 | int _getHighestUID(const char *objectClass = "posixAccount", const char *attrname = "uidNumber"); 89 | void _ldapAdd(const std::string &dn, const std::multimap &attr, bool modify = false); 90 | void _ldapModify(const std::string &dn, const std::multimap &attr); 91 | void _ldapDelete(const std::string &dn); 92 | static int _unlink_cb(const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf); 93 | std::string _fileGetContents(const std::string &filename); 94 | void _filePutContents(const std::string &filename, const std::string &contents); 95 | std::string _ldapDN(); 96 | bool _activeDirectory(); 97 | std::string _uidField(); 98 | static void _dropFilesystemCapabilities(gpointer); 99 | }; 100 | 101 | #endif // PISERVER_H 102 | -------------------------------------------------------------------------------- /src/stpanalyzer.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Class to detect if the Spanning-Tree-Protocol 3 | * is enabled on the Ethernet switch (which can 4 | * interfere with network booting) 5 | * 6 | * Detection only works when Piserver is installed 7 | * bare-metal on a server directly connected to 8 | * the network (not under with Vmware, Virtualbox, etc.) 9 | */ 10 | 11 | #include "stpanalyzer.h" 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | struct stp_packet { 22 | /* Ethernet header */ 23 | struct ethhdr eth; 24 | /* 802.2 LLC header */ 25 | uint8_t dsap, ssap, control; 26 | /* STP fields */ 27 | uint16_t protocol; 28 | uint8_t version; 29 | uint8_t msgtype; 30 | uint8_t flags; 31 | uint16_t rootpriority; 32 | unsigned char rootmac[6]; 33 | uint32_t rootpathcost; 34 | uint16_t bridgepriority; 35 | unsigned char bridgemac[6]; 36 | uint16_t portid; 37 | uint16_t msgage; 38 | uint16_t maxage; 39 | uint16_t hellotime; 40 | uint16_t forwarddelay; 41 | } __attribute__((__packed__)); 42 | 43 | #define LSAP_BDPU 0x42 44 | #define MULTICAST_MAC_BDPU {0x1, 0x80, 0xC2, 0, 0, 0} 45 | #define MULTICAST_MAC_BDPUPV {0x1, 0, 0x0C, 0xCC, 0xCC, 0xCD} 46 | 47 | StpAnalyzer::StpAnalyzer(int onlyReportIfForwardDelayIsAbove) 48 | : _s(-1), _minForwardDelay(onlyReportIfForwardDelayIsAbove) 49 | { 50 | } 51 | 52 | StpAnalyzer::~StpAnalyzer() 53 | { 54 | stopListening(); 55 | } 56 | 57 | void StpAnalyzer::startListening(const std::string &ifname) 58 | { 59 | int iface; 60 | 61 | _s = socket(AF_PACKET,SOCK_RAW,htons(ETH_P_802_2)); 62 | if (_s < 0) 63 | { 64 | //throw std::runtime_error("Error creating STP listen socket"); 65 | return; 66 | } 67 | 68 | iface = if_nametoindex(ifname.c_str()); 69 | if (!iface) { 70 | //throw std::runtime_error("Error obtaining interface id"); 71 | return; 72 | } 73 | 74 | struct packet_mreq mreq = { iface, PACKET_MR_MULTICAST, ETH_ALEN, MULTICAST_MAC_BDPU }; 75 | setsockopt(_s, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mreq, sizeof(mreq)); 76 | struct packet_mreq mreq2 = { iface, PACKET_MR_MULTICAST, ETH_ALEN, MULTICAST_MAC_BDPUPV }; 77 | setsockopt(_s, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mreq2, sizeof(mreq2)); 78 | 79 | _conn = Glib::signal_io().connect(sigc::mem_fun(this, &StpAnalyzer::on_packet), _s, Glib::IO_IN); 80 | } 81 | 82 | void StpAnalyzer::stopListening() 83 | { 84 | if (_s != -1) 85 | { 86 | _conn.disconnect(); 87 | close(_s); 88 | } 89 | } 90 | 91 | bool StpAnalyzer::on_packet(Glib::IOCondition) 92 | { 93 | struct stp_packet packet = { 0 }; 94 | char strbuf[64]; 95 | 96 | int len = recvfrom(_s, &packet, sizeof(packet), 0, NULL, 0); 97 | 98 | if (len == sizeof(packet) 99 | && packet.dsap == LSAP_BDPU 100 | && packet.ssap == LSAP_BDPU 101 | && packet.protocol == 0) 102 | { 103 | /* It is a STP packet */ 104 | int forwardDelay = GINT_FROM_LE(packet.forwarddelay); 105 | if (forwardDelay > _minForwardDelay) 106 | { 107 | snprintf(strbuf, sizeof(strbuf), "MAC: %.2x:%.2x:%.2x:%.2x:%.2x:%.2x forward delay: %d", 108 | packet.eth.h_source[0], packet.eth.h_source[1], packet.eth.h_source[2], 109 | packet.eth.h_source[3], packet.eth.h_source[4], packet.eth.h_source[5], forwardDelay); 110 | std::string extrainfo = strbuf; 111 | _signal_detected.emit(extrainfo); 112 | } 113 | 114 | /* Only analyze first packet */ 115 | stopListening(); 116 | } 117 | 118 | return true; 119 | } 120 | -------------------------------------------------------------------------------- /src/stpanalyzer.h: -------------------------------------------------------------------------------- 1 | #ifndef STPANALYZER_H 2 | #define STPANALYZER_H 3 | 4 | /** 5 | * Class to detect if the Spanning-Tree-Protocol 6 | * is enabled on an Ethernet switch (which can 7 | * interfere with network booting) 8 | * 9 | * Detection only works when Piserver is installed 10 | * bare-metal on a server directly connected to 11 | * the network (not under with Vmware, Virtualbox, etc.) 12 | */ 13 | 14 | #include 15 | #include 16 | 17 | class StpAnalyzer 18 | { 19 | public: 20 | StpAnalyzer(int onlyReportIfForwardDelayIsAbove = 5); 21 | virtual ~StpAnalyzer(); 22 | void startListening(const std::string &ifname); 23 | void stopListening(); 24 | 25 | /* signals */ 26 | inline sigc::signal signal_detected() 27 | { 28 | return _signal_detected; 29 | } 30 | 31 | protected: 32 | int _s, _minForwardDelay; 33 | sigc::connection _conn; 34 | sigc::signal _signal_detected; 35 | 36 | /* Slots */ 37 | bool on_packet(Glib::IOCondition cond); 38 | }; 39 | 40 | #endif // STPANALYZER_H 41 | -------------------------------------------------------------------------------- /src/user.cpp: -------------------------------------------------------------------------------- 1 | #include "user.h" 2 | #include 3 | 4 | User::User(const std::string &dn, const std::string &name, const std::string &description, std::string lastLoginStr) 5 | : _dn(dn), _name(name), _description(description), _lastLoginTime(nullptr) 6 | { 7 | if (lastLoginStr.size() > 14) 8 | { 9 | if (lastLoginStr[8] != 'T') 10 | { 11 | /* LDAP typically uses dates like 20190607130317Z 12 | glib expects 20190607T130317Z */ 13 | lastLoginStr.insert(8, 1, 'T'); 14 | } 15 | _lastLoginTime = g_date_time_new_from_iso8601(lastLoginStr.c_str(), NULL); 16 | } 17 | } 18 | 19 | User::User(const User &u) 20 | { 21 | _dn = u._dn; 22 | _name = u._name; 23 | _description = u._description; 24 | _lastLoginTime = u._lastLoginTime; 25 | if (_lastLoginTime) 26 | { 27 | g_date_time_ref(_lastLoginTime); 28 | } 29 | } 30 | 31 | User::~User() 32 | { 33 | if (_lastLoginTime) 34 | { 35 | g_date_time_unref(_lastLoginTime); 36 | } 37 | } 38 | 39 | const std::string User::lastlogin(const char *format) const 40 | { 41 | if (!_lastLoginTime) 42 | return ""; 43 | 44 | gchar *gstr = g_date_time_format(_lastLoginTime, format); 45 | if (!gstr) 46 | return ""; 47 | 48 | std::string result(gstr); 49 | g_free(gstr); 50 | 51 | return result; 52 | } 53 | -------------------------------------------------------------------------------- /src/user.h: -------------------------------------------------------------------------------- 1 | #ifndef USER_H 2 | #define USER_H 3 | 4 | #include 5 | #include 6 | 7 | class User 8 | { 9 | public: 10 | User(const std::string &dn, const std::string &name, const std::string &description, std::string lastLoginStr); 11 | 12 | User(const User &u); 13 | 14 | virtual ~User(); 15 | 16 | inline const std::string &name() const 17 | { 18 | return _name; 19 | } 20 | 21 | inline const std::string &dn() const 22 | { 23 | return _dn; 24 | } 25 | 26 | inline const std::string &description() const 27 | { 28 | return _description; 29 | } 30 | 31 | const std::string lastlogin(const char *format = "%Y-%m-%d") const; 32 | 33 | protected: 34 | std::string _dn, _name, _description, _lastLogin; 35 | GDateTime *_lastLoginTime; 36 | }; 37 | 38 | #endif // USER_H 39 | --------------------------------------------------------------------------------