Compare commits

...

58 Commits

Author SHA1 Message Date
Mario Fetka
ba8211e0da Logging stderr 2026-05-21 23:03:32 +02:00
Mario Fetka
efc90d8bfe Logging 2026-05-21 22:49:46 +02:00
Mario Fetka
c1483af0cb Apply Bindery no need to write config 2026-05-21 22:45:45 +02:00
Mario Fetka
23c36384c0 Web Logout 2026-05-21 22:29:29 +02:00
Mario Fetka
c7c746d264 Weblogin 2026-05-21 21:56:28 +02:00
Mario Fetka
4b56266392 Add back smart_userlist 2026-05-21 21:26:13 +02:00
Mario Fetka
6267ac3bf1 roleback 2026-05-21 21:12:44 +02:00
Mario Fetka
7ce22da3ea Userlist 2026-05-21 20:48:37 +02:00
Mario Fetka
544afcd296 Cups support language idependent 2026-05-21 19:12:55 +02:00
Mario Fetka
a2f4f8a789 Cups support 2026-05-21 18:58:59 +02:00
Mario Fetka
339aa49d6b Settings again rounded 2026-05-21 18:40:47 +02:00
Mario Fetka
dd478fe889 Back to smart wouthout debug 2026-05-21 18:27:11 +02:00
Mario Fetka
adf4081dfc control with more output and status 2026-05-21 18:23:49 +02:00
Mario Fetka
1f032a1d43 Header/Footer + HTML-Escaping + Delete-Confirm 2026-05-21 17:32:39 +02:00
Mario Fetka
ec32733dab add drop down menu for interfaces with IPX active 2026-05-21 17:11:06 +02:00
Mario Fetka
6aec044c52 add drop down menu for interfaces 2026-05-21 16:59:55 +02:00
Mario Fetka
97ac40f7a1 add back button 2026-05-21 16:45:30 +02:00
Mario Fetka
aa60166da6 add control 2026-05-21 16:30:41 +02:00
Mario Fetka
362be76b7c activate control 2026-05-21 16:28:10 +02:00
Mario Fetka
1446e495dc activate control 2026-05-21 16:17:35 +02:00
Mario Fetka
7d4a2f1b99 activate control 2026-05-21 16:04:16 +02:00
Mario Fetka
d763467440 correct write path 2026-05-21 15:48:09 +02:00
Mario Fetka
7d71436bb3 roleback Smart UI 2026-05-21 15:36:54 +02:00
Mario Fetka
edd63ea3fd roleback Smart UI 2026-05-21 15:18:36 +02:00
Mario Fetka
feb12eb0c5 Update to new design 2026-04-22 19:11:36 +02:00
Mario Fetka
5a69a9532a back to working 2026-04-22 17:07:29 +02:00
Mario Fetka
b5d0fc983e back to working 2026-04-22 17:06:18 +02:00
Mario Fetka
866ff1688c update logos 2026-04-22 16:54:41 +02:00
Mario Fetka
bfdfd87ec3 Update Readme with the new -l from nwwebui 2026-04-22 14:26:57 +02:00
Mario Fetka
bf9f9de5d4 add logfile to smart.conf 2026-04-22 14:12:59 +02:00
Mario Fetka
39bf88dd3b add commandline paramter for logfile 2026-04-22 14:10:55 +02:00
Mario Fetka
43cb959dc2 Update Readme.md 2026-04-22 13:47:46 +02:00
Mario Fetka
ee57cb486d Update Readme.md 2026-04-22 13:42:38 +02:00
Mario Fetka
3351044f68 makdir pid/log dir 2026-04-21 20:07:41 +02:00
Mario Fetka
af08efb6ad Add pam.d smart file 2026-04-21 18:09:30 +02:00
Mario Fetka
349dba63d6 Add pam.d smart file 2026-04-21 18:08:00 +02:00
Mario Fetka
a4dd6abc12 Add systemd service file 2026-04-21 17:57:18 +02:00
Mario Fetka
0e69a47827 set verison in html file 2026-04-21 16:22:04 +02:00
Mario Fetka
94e2ccc6e4 back to old file 2026-04-21 16:04:52 +02:00
Mario Fetka
7ee884fe38 Add version info and optimize log for smart.log 2026-04-21 15:56:28 +02:00
Mario Fetka
3f0fd53a33 back to old file 2026-04-21 15:42:58 +02:00
Mario Fetka
b676c8f5ce Add version info and optimize log for smart.log 2026-04-21 15:31:58 +02:00
Mario Fetka
a4dfee99e2 Add version info adn damon support 2026-04-21 15:19:26 +02:00
Mario Fetka
8e7c97ff7f make config handel the markers 2026-04-21 13:45:04 +02:00
Mario Fetka
b7206fc83a make config handling more user friendly 2026-04-21 12:00:30 +02:00
Mario Fetka
2ee152f543 add more child handling 2026-04-21 11:46:35 +02:00
Mario Fetka
829be767c9 default to http 2026-04-21 10:24:02 +02:00
Mario Fetka
a4d3ded77e more docu and enable loglevel for nwwebui 2026-04-21 09:48:15 +02:00
Mario Fetka
0d3bbdec8e Switch to FindPackage 2026-04-21 08:53:11 +02:00
Mario Fetka
ed3acbe2d4 Correct LIBEXECDIR 2026-04-21 08:34:09 +02:00
Mario Fetka
9670c4e749 add missing #include <string.h> 2026-04-21 06:08:38 +02:00
Mario Fetka
a3f7460150 add missing #include <stdlib.h> 2026-04-21 06:05:45 +02:00
Mario Fetka
476c51ac10 Update typo in cmake file 2026-04-21 05:57:58 +02:00
Mario Fetka
5bbe69ac5f Update cmake 2026-04-21 05:32:04 +02:00
Mario Fetka
331fb1a746 Add smart 2026-04-21 04:52:48 +02:00
Mario Fetka
bb9392b9b0 Add smart 2026-04-21 04:52:41 +02:00
Mario Fetka
24f48e1f96 ad Readme.md 2026-04-21 02:19:48 +02:00
Mario Fetka
0a31d87196 Ignore generated log file 2026-04-21 02:18:56 +02:00
49 changed files with 6264 additions and 1040 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
error.log

141
CMakeLists.txt Normal file
View File

@@ -0,0 +1,141 @@
#################################
# Project
##############
#################################
# Dependencies
##############
#################################
# Generated files
##############
# systemd itself is detected by the top-level systemdservice.cmake.
# This webui submodule only consumes WITH_SYSTEMD and SYSTEMD_SERVICES_INSTALL_DIR.
if(NOT DEFINED MARS_NWE_SYSTEMD_SERVICE)
set(MARS_NWE_SYSTEMD_SERVICE "mars-nwe-serv.service" CACHE STRING "MARS_NWE systemd service name")
endif()
if(NOT DEFINED SYSTEMCTL_EXECUTABLE)
find_program(SYSTEMCTL_EXECUTABLE systemctl)
endif()
if(NOT SYSTEMCTL_EXECUTABLE)
set(SYSTEMCTL_EXECUTABLE "/usr/bin/systemctl")
endif()
configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/config.h.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/config.h"
IMMEDIATE @ONLY)
configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/smart.conf.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/smart.conf"
IMMEDIATE @ONLY)
configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/smart.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/smart"
IMMEDIATE @ONLY)
configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/control.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/control"
IMMEDIATE @ONLY)
configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/static/start.html.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/static/start.html"
IMMEDIATE @ONLY)
configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/mars-nwe-webui.service.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/mars-nwe-webui.service"
@ONLY
)
#################################
# Compiler Switches
##############
INCLUDE_DIRECTORIES(
${CMAKE_CURRENT_BINARY_DIR}
${CMAKE_SOURCE_DIR}/include
${CMAKE_BINARY_DIR}/include
)
#################################
# Source Files
##############
add_executable(nwwebui nwwebui.c)
add_executable(check_login check_login.c)
add_executable(smart_userlist smart_userlist.c)
#################################
# Linking
##############
target_link_libraries(nwwebui
OpenSSL::SSL
OpenSSL::Crypto
)
target_link_libraries(check_login
${PAM_LIBRARY}
${DL_LIBRARY}
)
target_link_libraries(smart_userlist
${PAM_LIBRARY}
${DL_LIBRARY}
)
#################################
# Install Files
##############
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/smart.conf DESTINATION ${MARS_NWE_INSTALL_FULL_CONFDIR})
install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/smart DESTINATION ${MARS_NWE_INSTALL_FULL_LIBEXECDIR})
install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/control DESTINATION ${MARS_NWE_INSTALL_FULL_LIBEXECDIR})
install(FILES smart.pamd DESTINATION ${CMAKE_INSTALL_FULL_SYSCONFDIR}/pam.d RENAME smart)
install(PROGRAMS apply.pl DESTINATION ${MARS_NWE_INSTALL_FULL_LIBEXECDIR})
install(PROGRAMS readconfig.pl DESTINATION ${MARS_NWE_INSTALL_FULL_LIBEXECDIR})
install(PROGRAMS settings.pl DESTINATION ${MARS_NWE_INSTALL_FULL_LIBEXECDIR})
install(PROGRAMS static.pl DESTINATION ${MARS_NWE_INSTALL_FULL_LIBEXECDIR})
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/static/start.html DESTINATION ${MARS_NWE_INSTALL_FULL_LIBEXECDIR}/static)
install(FILES static/menu.html DESTINATION ${MARS_NWE_INSTALL_FULL_LIBEXECDIR}/static)
install(FILES static/smart_icon.jpg DESTINATION ${MARS_NWE_INSTALL_FULL_LIBEXECDIR}/static)
install(FILES static/smart.jpg DESTINATION ${MARS_NWE_INSTALL_FULL_LIBEXECDIR}/static)
install(FILES static/favicon.ico DESTINATION ${MARS_NWE_INSTALL_FULL_LIBEXECDIR}/static)
install(FILES static/favicon-32x32.png DESTINATION ${MARS_NWE_INSTALL_FULL_LIBEXECDIR}/static)
install(FILES static/favicon-16x16.png DESTINATION ${MARS_NWE_INSTALL_FULL_LIBEXECDIR}/static)
install(FILES static/apple-touch-icon.png DESTINATION ${MARS_NWE_INSTALL_FULL_LIBEXECDIR}/static)
install(FILES static/icon-start.svg DESTINATION ${MARS_NWE_INSTALL_FULL_LIBEXECDIR}/static)
install(FILES static/icon-service.svg DESTINATION ${MARS_NWE_INSTALL_FULL_LIBEXECDIR}/static)
install(FILES static/icon-general.svg DESTINATION ${MARS_NWE_INSTALL_FULL_LIBEXECDIR}/static)
install(FILES static/icon-dirs.svg DESTINATION ${MARS_NWE_INSTALL_FULL_LIBEXECDIR}/static)
install(FILES static/icon-configh.svg DESTINATION ${MARS_NWE_INSTALL_FULL_LIBEXECDIR}/static)
install(FILES static/icon-security.svg DESTINATION ${MARS_NWE_INSTALL_FULL_LIBEXECDIR}/static)
install(FILES static/icon-susers.svg DESTINATION ${MARS_NWE_INSTALL_FULL_LIBEXECDIR}/static)
install(FILES static/icon-volumes.svg DESTINATION ${MARS_NWE_INSTALL_FULL_LIBEXECDIR}/static)
install(FILES static/icon-devices.svg DESTINATION ${MARS_NWE_INSTALL_FULL_LIBEXECDIR}/static)
install(FILES static/icon-logging.svg DESTINATION ${MARS_NWE_INSTALL_FULL_LIBEXECDIR}/static)
install(FILES static/icon-smart.svg DESTINATION ${MARS_NWE_INSTALL_FULL_LIBEXECDIR}/static)
install(FILES static/icon-stations.svg DESTINATION ${MARS_NWE_INSTALL_FULL_LIBEXECDIR}/static)
install(FILES static/icon-users.svg DESTINATION ${MARS_NWE_INSTALL_FULL_LIBEXECDIR}/static)
install(FILES static/icon-groups.svg DESTINATION ${MARS_NWE_INSTALL_FULL_LIBEXECDIR}/static)
install(FILES static/icon-queues.svg DESTINATION ${MARS_NWE_INSTALL_FULL_LIBEXECDIR}/static)
if(WITH_SYSTEMD)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/mars-nwe-webui.service DESTINATION ${SYSTEMD_SERVICES_INSTALL_DIR})
endif()
install(TARGETS check_login DESTINATION ${MARS_NWE_INSTALL_FULL_LIBEXECDIR})
install(TARGETS smart_userlist DESTINATION ${MARS_NWE_INSTALL_FULL_LIBEXECDIR})
install(TARGETS nwwebui DESTINATION ${CMAKE_INSTALL_SBINDIR})

276
README.md Normal file
View File

@@ -0,0 +1,276 @@
# SMArT
SMArT is the web-based configuration interface for **MARS_NWE**, a Novell NetWare 3.x emulator for Linux and FreeBSD.
In the current setup, this repository is no longer treated as a standalone component only. It is integrated into the main `mars_nwe` project as a **Git submodule** and is therefore included in the normal **mars_nwe release** process.
## Project status and integration
This repository is intended to be embedded into the main `mars_nwe` Git repository:
- Main project: `mars_nwe`
- Submodule role: provides the SMArT web UI and helper tools
- Release model: shipped as part of the integrated **MARS_NWE** release, not as a separate end-user release artifact
The build and install rules show that the web UI binaries, Perl helpers, configuration, static assets, and PAM file are installed as part of the overall build and installation flow.
## Architecture overview
SMArT consists of two main parts:
1. **Perl-based application logic** for configuration pages and helper scripts
2. **`nwwebui` service** as the web frontend that exposes the application over HTTP and HTTPS
The current implementation adds a dedicated `nwwebui` service that can serve the application directly over:
- **HTTP on port 9080**
- **HTTPS on port 9443**
The service supports TLS via OpenSSL and can run both listeners in parallel. HTTPS is the preferred mode because authentication happens more securely over an encrypted connection, while plain HTTP may still be useful for testing or trusted internal environments.
## Security model
SMArT uses PAM-based authentication through the `check_login` helper. The supplied PAM policy is a standard `pam_unix` stack for authentication, account, password, and session handling. During installation with **MARS_NWE**, this file is installed automatically as:
- `/etc/pam.d/smart`
That means no manual PAM file deployment is normally required when SMArT is installed through the integrated `mars_nwe` package or release.
## Installed components
The install rules include the following relevant components.
### Binaries
- `nwwebui` dedicated web service frontend
- `check_login` PAM authentication helper
### Perl helpers
- `smart`
- `apply.pl`
- `readconfig.pl`
- `settings.pl`
- `static.pl`
### Configuration and assets
- `smart.conf`
- static HTML and image assets for the web UI
- optional `mars-nwe-webui.service` systemd unit
- PAM file installed as `/etc/pam.d/smart`
These components are all installed by the build system as part of the same integrated installation target.
## Typical runtime paths
The original templates use CMake placeholders. For documentation, the following standard example paths can be used in a typical Linux installation:
- Main MARS_NWE config directory: `/etc/mars_nwe`
- SMArT config file: `/etc/mars_nwe/smart.conf`
- Main MARS_NWE server config: `/etc/mars_nwe/nwserv.conf`
- Helper binaries and scripts: `/usr/libexec/mars_nwe`
- Static SMArT assets: `/usr/libexec/mars_nwe/static`
- Log directory: `/var/log/mars_nwe`
- PID directory: `/run/mars_nwe`
- TLS certificate: `/etc/mars_nwe/server.crt`
- TLS private key: `/etc/mars_nwe/server.key`
- PAM file: `/etc/pam.d/smart`
These values are sensible standard defaults for documentation. Packaging may still adjust them depending on the target distribution.
## The `smart.conf` file
The `smart.conf` file controls both the SMArT frontend behavior and the `nwwebui` listener settings.
A documented example with standard installation paths is shown below:
```perl
# SMArT / nwwebui configuration file
# ------------------------------------------------------------
# UI colors
# ------------------------------------------------------------
$COLOR_BACK = "#F0F0FF";
$COLOR_HEAD_BACK = "#C0C0FF";
$COLOR_HEAD_FORE = "#000000";
$COLOR_SUBH_BACK = "#D0D0FF";
$COLOR_SUBH_FORE = "#000000";
$COLOR_TEXT_BACK = "#E0E0FF";
$COLOR_TEXT_FORE = "#000000";
# ------------------------------------------------------------
# Main MARS_NWE configuration
# ------------------------------------------------------------
$mars_config = '/etc/mars_nwe/nwserv.conf';
$nonroot_user = 'nobody';
$smart_compact_nwservconf = 0;
# ------------------------------------------------------------
# SMArT internal file layout
# ------------------------------------------------------------
$smart_conf_path = '/etc/mars_nwe/smart.conf';
$smart_nwclient_path = '/etc/mars_nwe/.nwclient';
$smart_static_dir = '/usr/libexec/mars_nwe/static';
$smart_log_path = '/var/log/mars_nwe/smart.log';
$smart_check_login = '/usr/libexec/mars_nwe/check_login';
# Optional override, usually not needed
# $smart_perl_path = '/usr/libexec/mars_nwe/smart';
# ------------------------------------------------------------
# nwwebui listener settings
# ------------------------------------------------------------
$nw_bind_ip = '0.0.0.0';
$nw_log_level = 1;
$nw_daemonize = 0;
$nw_pid_file = '/run/mars_nwe/nwwebui.pid';
$nw_log_file = '/var/log/mars_nwe/nwwebui.log';
$nw_ssl_enable = 0;
$nw_http_port = 9080;
$nw_https_port = 9443;
$nw_cert_file = '/etc/mars_nwe/server.crt';
$nw_key_file = '/etc/mars_nwe/server.key';
```
## `smart.conf` settings explained
### UI colors
These variables define the default SMArT page colors:
- `$COLOR_BACK` page background
- `$COLOR_HEAD_BACK` / `$COLOR_HEAD_FORE` main section header colors
- `$COLOR_SUBH_BACK` / `$COLOR_SUBH_FORE` subsection header colors
- `$COLOR_TEXT_BACK` / `$COLOR_TEXT_FORE` regular content row colors
### Main MARS_NWE configuration
- `$mars_config` path to the main `nwserv.conf` file that SMArT reads and updates
- `$nonroot_user` unprivileged user account used when SMArT drops privileges
- `$smart_compact_nwservconf` controls how `nwserv.conf` is written back:
- `0` keeps comments, spacing, and the original structure as much as possible
- `1` writes a more compact version without the original long comment layout
### SMArT internal file layout
- `$smart_conf_path` absolute path to `smart.conf`
- `$smart_nwclient_path` file used to store bindery login information for SMArT helper tools
- `$smart_static_dir` directory containing HTML, icons, and other static assets
- `$smart_log_path` log file used by the Perl-based SMArT frontend
- `$smart_check_login` PAM-based authentication helper path
- `$smart_perl_path` optional override for the main SMArT Perl executable; usually not needed
### `nwwebui` listener settings
- `$nw_bind_ip` bind address for HTTP and HTTPS listeners, for example `0.0.0.0` for all IPv4 interfaces or `127.0.0.1` for localhost-only access
- `$nw_log_level` service log verbosity:
- `0` = errors only
- `1` = informational messages
- `2` = debug output
- `$nw_daemonize` whether `nwwebui` detaches into the background
- `$nw_pid_file` location of the PID file
- `$nw_log_file` log file written by `nwwebui`
- `$nw_ssl_enable` enables or disables HTTPS support
- `$nw_http_port` HTTP listener port; set to `0` to disable plain HTTP
- `$nw_https_port` HTTPS listener port; set to `0` to disable HTTPS
- `$nw_cert_file` PEM certificate path for TLS
- `$nw_key_file` PEM private key path for TLS
The current code and template show that:
- `nwwebui` listens on `9080` for HTTP by default
- `9443` is used for HTTPS
- the log file can be configured with `$nw_log_file`
- the log path can also be overridden at runtime with `-l`
Because passwords may be transmitted during login, HTTPS is the recommended way to access the interface.
## Starting the service
Depending on the installation method, `nwwebui` can be started either via **systemd** or directly from the **command line**. The build system installs a `mars-nwe-webui.service` unit when systemd support is enabled.
### Starting with systemd
A typical installed system uses the `mars-nwe-webui.service` unit. The unit starts `nwwebui`, prepares the needed runtime directories, and loads the standard `smart.conf` file.
Typical commands:
```bash
systemctl enable --now mars-nwe-webui.service
systemctl start mars-nwe-webui.service
systemctl stop mars-nwe-webui.service
systemctl restart mars-nwe-webui.service
systemctl status mars-nwe-webui.service
```
The service unit starts `nwwebui` with the equivalent of:
```bash
/usr/sbin/nwwebui -c /etc/mars_nwe/smart.conf
```
### Starting from the command line
`nwwebui` can also be launched manually. The built-in usage text documents the supported options:
```text
Usage: nwwebui [-h] [-d] [-s] [-c <smart.conf>] [-p <pidfile>] [-l <logfile>]
Options:
-h, --help Show this help text and exit
-d, --daemon Run in daemon mode
-s, --stop Stop the running nwwebui instance
-c, --config <file> Use an alternate smart.conf path
-p, --pidfile <file> Override PID file path
-l, --logfile <file> Override log file path
```
Typical examples:
```bash
# start in foreground with the standard configuration
/usr/sbin/nwwebui -c /etc/mars_nwe/smart.conf
# start in daemon mode
/usr/sbin/nwwebui -d -c /etc/mars_nwe/smart.conf
# stop a running instance
/usr/sbin/nwwebui -s -c /etc/mars_nwe/smart.conf
# use a custom PID file
/usr/sbin/nwwebui -d -c /etc/mars_nwe/smart.conf -p /run/mars_nwe/nwwebui.pid
# use a custom log file
/usr/sbin/nwwebui -c /etc/mars_nwe/smart.conf -l /var/log/mars_nwe/custom-nwwebui.log
# override both PID and log file
/usr/sbin/nwwebui -d -c /etc/mars_nwe/smart.conf -p /run/mars_nwe/nwwebui.pid -l /var/log/mars_nwe/nwwebui.log
```
The `-l` option overrides `$nw_log_file` from `smart.conf` at runtime.
Typical access URLs:
- `http://<host>:9080/`
- `https://<host>:9443/`
For production use, HTTPS should be preferred.
## Build and installation notes
This repository is built as part of the main `mars_nwe` build. The build system:
- generates `smart.conf` from the template
- generates the `smart` launcher script
- builds `nwwebui`
- builds `check_login`
- installs the PAM file and static UI assets
Because this repository is integrated as a Git submodule in `mars_nwe`, end users normally consume it through the main `mars_nwe` source tree and release packages rather than using it as a standalone project.
## Summary
SMArT is now an integrated part of the `mars_nwe` release and includes a dedicated `nwwebui` service that can expose the interface over both HTTP and HTTPS. The standard listener ports are **9080** for HTTP and **9443** for HTTPS. Authentication is handled through PAM, and the required `/etc/pam.d/smart` file is installed automatically together with the integrated MARS_NWE installation.

183
apply.pl
View File

@@ -22,12 +22,54 @@
#
#
sub read_smart_bindery_values()
{
my( $bind_server, $bind_user, $bind_pass ) = ( '', '', '' );
if( open( my $fh, '<' . $smart_nwclient_path ) )
{
my $line = <$fh>;
close( $fh );
chomp( $line ) if defined( $line );
( $bind_server, $bind_user, $bind_pass ) = split( '[/ ]', $line ) if defined( $line );
}
$bind_server = '' unless defined( $bind_server );
$bind_user = '' unless defined( $bind_user );
$bind_pass = '' unless defined( $bind_pass );
return( $bind_server, $bind_user, $bind_pass );
}
sub write_smart_bindery_values( $$$ )
{
my( $bind_server, $bind_user, $bind_pass ) = @_;
$bind_server = '' unless defined( $bind_server );
$bind_user = '' unless defined( $bind_user );
$bind_pass = '' unless defined( $bind_pass );
open( my $fh, '>' . $smart_nwclient_path ) or die "Could not open $smart_nwclient_path: $!";
print( $fh $bind_server . '/' . $bind_user . ' ' . $bind_pass . "\n" );
close( $fh );
chown( scalar( getpwnam( $nonroot_user ) ), 0, $smart_nwclient_path );
chmod( 0600, $smart_nwclient_path );
}
sub handle_request()
{
if( $c[1] eq 'general' )
{
setc( 2, server_name );
setc( 3, internal_net );
if( defined( $p{sync_smart_bind_server} ) && $p{sync_smart_bind_server} eq 'on' && defined( $p{server_name} ) && $p{server_name} ne '' )
{
my( $old_bind_server, $old_bind_user, $old_bind_pass ) = read_smart_bindery_values();
write_smart_bindery_values( $p{server_name}, $old_bind_user, $old_bind_pass );
}
setc( 16, test );
setc( 210, timing_down );
setc( 211, timing_warn );
@@ -109,6 +151,7 @@ sub handle_request()
{
delconfigline( '4 ' . $c[2] );
}
$p{interface} =~ s/[^-_\.A-Za-z0-9:\*]//g;
if( $p{number} ne '' )
{
addconfigline( '4 ' . $p{number} . ' ' . $p{interface} . ' ' . $p{frametype} . ' ' . $p{delay} );
@@ -138,17 +181,18 @@ sub handle_request()
if( $p{mars_config} ne $mars_config )
{
# Just append the line. Messy but easy. ;)
open( FILE, '>>' . $base . 'smart.conf' );
open( FILE, '>>' . $smart_conf_path ) or die "Could not open $smart_conf_path: $!";
print( FILE "\n" . '$mars_config = \'' . $p{mars_config} . '\';' . "\n" );
close( FILE );
}
open( FILE, '>' . $base . '.nwclient' );
print( FILE $p{bind_server} . '/' . $p{bind_user} . ' ' . $p{bind_pass } . "\n" );
close( FILE );
chown( scalar( getpwnam( $nonroot_user ) ), 0, $base . '.nwclient' );
chmod( 0600, $base . '.nwclient' );
write_smart_bindery_values( $p{bind_server}, $p{bind_user}, $p{bind_pass} );
if( defined( $p{sync_general_server_name} ) && $p{sync_general_server_name} eq 'on' && defined( $p{bind_server} ) && $p{bind_server} ne '' )
{
$p{server_name} = $p{bind_server};
setc( 2, server_name );
}
}
elsif( $c[1] eq 'users' )
{
@@ -227,6 +271,7 @@ EOF
}
redirect( '/settings/users' );
return( 1 );
}
elsif( $c[1] eq 'groups' )
{
@@ -280,6 +325,7 @@ EOF
}
redirect( '/settings/groups' );
return( 1 );
}
elsif( $c[1] eq 'queues' )
{
@@ -349,7 +395,94 @@ EOF
}
redirect( '/settings/queues' );
return( 1 );
}
elsif( $c[1] eq 'advanced' )
{
my $advanced_category = $p{advanced_category};
$advanced_category = 'all' if ! defined( $advanced_category ) || $advanced_category eq '';
my $save_devices = ( $advanced_category eq 'all' || $advanced_category eq 'devices' );
my $save_security = ( $advanced_category eq 'all' || $advanced_category eq 'security' );
my $save_users = ( $advanced_category eq 'all' || $advanced_category eq 'users' );
my $save_queues = ( $advanced_category eq 'all' || $advanced_category eq 'queues' );
my $save_configh = ( $advanced_category eq 'all' || $advanced_category eq 'configh' );
my $save_stations = ( $advanced_category eq 'all' || $advanced_category eq 'stations' );
my $save_network = ( $advanced_category eq 'all' || $advanced_category eq 'network' );
if( $save_devices )
{
my $device_flags = 0;
$device_flags += 1 if $p{dev_keep} eq 'on';
$device_flags += 2 if $p{dev_auto} eq 'on';
$device_flags += 4 if $p{dev_remove_all} eq 'on';
delconfigline( 5 );
addconfigline( '5 ' . $device_flags ) if $device_flags != 0;
}
if( $save_security )
{
my $security_flags = 0;
$security_flags += 0x8 if $p{sec_supervisor_ignore} eq 'on';
$security_flags += 0x40 if $p{sec_2gb_free} eq 'on';
$security_flags += 0x200 if $p{sec_int17} eq 'on';
$security_flags += 0x2 if $p{sec_delete_open} eq 'on';
$security_flags += 0x4 if $p{sec_rename_open} eq 'on';
delconfigline( 8 );
addconfigline( '8 ' . $security_flags ) if $security_flags != 0;
}
if( $save_users )
{
my $bindery_flags = 0;
$bindery_flags += 1 if $p{bindery_empty_scripts} eq 'on';
delconfigline( 17 );
addconfigline( '17 ' . $bindery_flags ) if $bindery_flags != 0;
}
if( $save_queues )
{
my $queue_flags = 0;
$queue_flags += 1 if $p{queue_no_banner} eq 'on';
delconfigline( 18 );
addconfigline( '18 ' . $queue_flags ) if $queue_flags != 0;
delconfigline( 22 );
foreach my $line ( split( /\r?\n/, $p{print_servers} ) )
{
$line =~ s/^\s+//;
$line =~ s/\s+$//;
next if $line eq '';
$line =~ s/#.*//;
next if $line eq '';
addconfigline( '22 ' . $line );
}
}
if( $save_configh )
{
setc( 50, conversion_tables );
setc( 80, max_dir_search_handles );
}
if( $save_network )
{
setc( 310, watchdogs );
}
if( $save_stations )
{
setc( 400, station_file );
setc( 401, nearest_replies );
setc( 402, connect_replies );
}
}
else
{
putp();
@@ -360,6 +493,42 @@ EOF
writeconfig();
}
sub add_mask_flag( $$$ )
{
my( $value, $param, $mask ) = @_;
if( defined( $p{$param} ) && $p{$param} ne '' )
{
$value |= $mask;
}
return $value;
}
sub addconfig_hex( $$ )
{
delconfigline( $_[0] );
addconfigline( $_[0] . ' 0x' . sprintf( '%x', $_[1] ) );
}
sub sanitize_simple_value( $ )
{
my $v = $_[0];
$v = '' unless defined $v;
$v =~ s/[\r\n]//g;
$v =~ s/^\s+//;
$v =~ s/\s+$//;
$v =~ s/[^-_\.\/\:\~A-Za-z0-9]//g;
return $v;
}
sub sanitize_number_value( $ )
{
my $v = $_[0];
$v = '' unless defined $v;
$v =~ s/[^0-9xXa-fA-F\-]//g;
return $v;
}
sub putp()
{
$cc =~ s/.*\?//;

Binary file not shown.

View File

@@ -23,6 +23,8 @@
#include <security/pam_appl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int my_conv(int num_msg, const struct pam_message **msg, struct pam_response **resp, void *appdata_ptr);

34
config.h.cmake Normal file
View File

@@ -0,0 +1,34 @@
#ifndef NWWEBUI_CONFIG_H
#define NWWEBUI_CONFIG_H
#define NWWEBUI_NAME "nwwebui"
#define NWWEBUI_VERSION "@MARS_NWE_VERSION@"
#define DEFAULT_SMART_CONF "@MARS_NWE_INSTALL_FULL_CONFDIR@/smart.conf"
#define DEFAULT_SMART_PERL "@MARS_NWE_INSTALL_FULL_LIBEXECDIR@/smart"
#define DEFAULT_CONTROL_PERL "@MARS_NWE_INSTALL_FULL_LIBEXECDIR@/control"
#define DEFAULT_MARS_SERVICE "@MARS_NWE_SYSTEMD_SERVICE@"
#define DEFAULT_SYSTEMCTL_PATH "@SYSTEMCTL_EXECUTABLE@"
#define LOG_PATH_DEFAULT "@MARS_NWE_LOG_DIR@/nwwebui.log"
#define LOG_LEVEL_ERROR 0
#define LOG_LEVEL_INFO 1
#define LOG_LEVEL_DEBUG 2
#define LOG_LEVEL_DEFAULT LOG_LEVEL_INFO
#define DEFAULT_BIND_IP "0.0.0.0"
#define DEFAULT_SSL_ENABLE 1
#define DEFAULT_HTTP_PORT 9080
#define DEFAULT_HTTPS_PORT 9443
#define DEFAULT_CERT_FILE "@MARS_NWE_INSTALL_FULL_CONFDIR@/server.crt"
#define DEFAULT_KEY_FILE "@MARS_NWE_INSTALL_FULL_CONFDIR@/server.key"
#define DEFAULT_PID_FILE "@MARS_NWE_PID_DIR@/nwwebui.pid"
#define DEFAULT_DAEMONIZE 0
#define NW_BACKLOG 64
#define NW_BUF_SZ 16384
#endif

297
control.cmake Normal file
View File

@@ -0,0 +1,297 @@
#!/usr/bin/perl
#
# SMArT service control helper for MARS_NWE systemd unit.
# Installed next to the other SMArT perl helpers.
#
use strict;
use warnings;
our ($server_id, $mars_nwe_service, $smart_systemctl_path, $cc, %p);
sub html_escape
{
my ($s) = @_;
$s = '' unless defined $s;
$s =~ s/&/&amp;/g;
$s =~ s/</&lt;/g;
$s =~ s/>/&gt;/g;
$s =~ s/"/&quot;/g;
return $s;
}
sub run_systemctl_command
{
my ($systemctl, @args) = @_;
my $output = '';
my ($reader, $writer);
if( ! pipe( $reader, $writer ) )
{
return (1, 'Could not create pipe: ' . $! . "\n");
}
my $pid = fork();
if( ! defined( $pid ) )
{
close( $reader );
close( $writer );
return (1, 'Could not fork: ' . $! . "\n");
}
if( $pid == 0 )
{
close( $reader );
# Capture stdout for the web UI. Leave stderr untouched so systemctl
# warnings/errors still go to smart.log, matching the old behaviour.
open( STDOUT, '>&', $writer ) or exit 127;
close( $writer );
exec( $systemctl, @args ) or do {
print 'Could not execute ' . $systemctl . ': ' . $! . "\n";
exit 127;
};
}
close( $writer );
while( my $line = <$reader> )
{
$output .= $line;
}
close( $reader );
waitpid( $pid, 0 );
my $rc = $? >> 8;
return ( $rc, $output );
}
sub wait_for_service_state
{
my ($systemctl, $service, $wanted, $timeout) = @_;
my $elapsed = 0;
my $last_state = '';
my $ok = 0;
$timeout = 45 unless defined($timeout) && $timeout > 0;
print '# waiting for ' . html_escape($service) . ' to become ' . html_escape($wanted) . "\n";
while( $elapsed <= $timeout )
{
my ($state_rc, $state_output) = run_systemctl_command( $systemctl, 'is-active', $service );
my $state = $state_output;
$state =~ s/[\r\n]+$//;
$state =~ s/^\s+//;
$state =~ s/\s+$//;
$state = 'unknown' if $state eq '';
if( $state ne $last_state )
{
print sprintf( "[%2ds] state: %s\n", $elapsed, html_escape($state) );
$last_state = $state;
}
if( $wanted eq 'inactive' )
{
if( $state eq 'inactive' || $state eq 'failed' )
{
$ok = 1;
last;
}
}
elsif( $wanted eq 'active' )
{
if( $state eq 'active' )
{
$ok = 1;
last;
}
if( $state eq 'failed' )
{
last;
}
}
sleep( 1 );
$elapsed++;
}
if( $ok )
{
print "wait result: reached $wanted\n";
return 0;
}
print "wait result: timeout after $timeout seconds\n";
return 1;
}
sub append_command_output
{
my ($title, $rc, $output) = @_;
print '# ' . html_escape($title) . "\n";
if( defined($output) && $output ne '' )
{
print html_escape($output);
}
else
{
print "(no output)\n";
}
print "exit code: $rc\n";
}
sub handle_request
{
local $| = 1;
my $service = $mars_nwe_service || '@MARS_NWE_SYSTEMD_SERVICE@';
my $systemctl = $smart_systemctl_path || '@SYSTEMCTL_EXECUTABLE@';
my $query = defined($cc) ? $cc : '';
my $action = '';
$query =~ s/^\?//;
if( $query =~ /^(start|stop|restart|status)$/ )
{
$action = $1;
}
elsif( $query =~ /(?:^|&)action=(start|stop|restart|status)(?:&|$)/ )
{
$action = $1;
}
elsif( defined($p{action}) && $p{action} =~ /^(start|stop|restart|status)$/ )
{
$action = $1;
}
print <<HTML_HEAD;
HTTP/1.0 200 OK
Content-Type: text/html
$server_id
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>MARS_NWE service control</title>
<link rel="icon" href="/static/favicon.ico" type="image/x-icon">
<style>
body{margin:0;padding:22px;background:#f6f1ea;color:#342a25;font-family:Arial,Helvetica,sans-serif}
a{color:#9f1f1f;text-decoration:none;font-weight:bold}a:hover{text-decoration:underline}
.box{max-width:900px;margin:0 auto;background:#fffdfa;border:1px solid #e5d6c6;border-radius:16px;padding:22px;box-shadow:0 10px 30px rgba(60,30,10,.10)}
h1{margin-top:0;color:#9f1f1f}.meta{padding:12px 14px;background:#fbf6ef;border:1px solid #ecdcc8;border-radius:12px;margin-bottom:14px}
pre{background:#2b241f;color:#f8eadc;padding:16px;border-radius:12px;overflow:auto;white-space:pre-wrap}.ok{color:#2f6f35;font-weight:bold}.bad{color:#9f1f1f;font-weight:bold}
.actions a{display:inline-block;margin:5px 8px 5px 0;padding:9px 12px;border-radius:10px;background:#9f1f1f;color:#fff}.actions a.secondary{background:#6f5b4f}
.waitbox{display:flex;align-items:center;gap:14px;margin:14px 0;padding:14px 16px;border:1px solid #ecdcc8;border-radius:14px;background:#fbf6ef;color:#6f5b4f}
.spinner{width:30px;height:30px;border:4px solid #e5d6c6;border-top-color:#9f1f1f;border-radius:50%;animation:spin .9s linear infinite;flex:0 0 auto}
\@keyframes spin{to{transform:rotate(360deg)}}
.waittitle{font-weight:bold;color:#9f1f1f}.waitsub{font-size:13px;margin-top:3px}
</style>
</head>
<body>
<div class="box">
<h1>MARS_NWE service control</h1>
HTML_HEAD
if( $action eq '' )
{
print "<p class=\"bad\">Invalid action.</p>\n";
print "<p>Allowed actions: start, stop, restart, status</p>\n";
print "<p><a href=\"/static/start.html\">Back</a></p>\n";
print "</div></body></html>\n";
return;
}
print '<div class="meta">Action: <b>' . html_escape($action) . '</b><br>' . "\n";
print 'Service: <b>' . html_escape($service) . '</b><br>' . "\n";
print 'systemctl: <b>' . html_escape($systemctl) . '</b></div>' . "\n";
if( $action ne 'status' )
{
my $wait_text = 'Waiting for service state change';
if( $action eq 'stop' )
{
$wait_text = 'Stopping service, waiting until it is inactive';
}
elsif( $action eq 'start' )
{
$wait_text = 'Starting service, waiting until it is active';
}
elsif( $action eq 'restart' )
{
$wait_text = 'Restarting service, waiting until it is active';
}
print '<div id="waitbox" class="waitbox">' . "\n";
print '<div class="spinner" aria-hidden="true"></div>' . "\n";
print '<div><div class="waittitle">' . html_escape($wait_text) . '</div>' . "\n";
print '<div class="waitsub">This can take up to 45 seconds. Please wait until the final status appears below.</div></div>' . "\n";
print '</div>' . "\n";
print " " x 4096; # help browsers render the waiting box before the command finishes
}
print "<pre>\n";
my $rc = 0;
my $output = '';
if( $action eq 'status' )
{
( $rc, $output ) = run_systemctl_command( $systemctl, '--no-pager', '--full', 'status', $service );
append_command_output( "systemctl --no-pager --full status $service", $rc, $output );
}
else
{
# Avoid repeated "unit file changed on disk" warnings after install/update.
my ( $reload_rc, $reload_output ) = run_systemctl_command( $systemctl, 'daemon-reload' );
append_command_output( 'systemctl daemon-reload', $reload_rc, $reload_output );
print "\n";
( $rc, $output ) = run_systemctl_command( $systemctl, $action, $service );
append_command_output( "systemctl $action $service", $rc, $output );
print "\n";
if( $rc == 0 )
{
my $wanted_state = ( $action eq 'stop' ) ? 'inactive' : 'active';
my $wait_rc = wait_for_service_state( $systemctl, $service, $wanted_state, 45 );
print "\n";
$rc = $wait_rc if $wait_rc != 0;
}
my ( $status_rc, $status_output ) = run_systemctl_command( $systemctl, '--no-pager', '--full', 'status', $service );
append_command_output( "systemctl --no-pager --full status $service", $status_rc, $status_output );
}
if( $rc == 0 )
{
print "</pre><script>var w=document.getElementById('waitbox');if(w){w.style.display='none';}</script><p class=\"ok\">Command completed successfully.</p>\n";
}
else
{
print "</pre><script>var w=document.getElementById('waitbox');if(w){w.style.display='none';}</script><p class=\"bad\">Command failed with exit code $rc.</p>\n";
}
print <<HTML_FOOT;
<div class="actions">
<a href="/service/control?status" class="secondary">Status</a>
<a href="/service/control?start">Start</a>
<a href="/service/control?stop" class="secondary">Stop</a>
<a href="/service/control?restart">Restart</a>
</div>
<p><a href="/static/start.html">Back</a></p>
</div>
</body>
</html>
HTML_FOOT
}
1;

View File

View File

@@ -0,0 +1,28 @@
[Unit]
Description=MARS NWE Web UI
After=network.target mars-nwe-serv.service
Wants=network.target
Documentation=man:systemd.service(5)
[Service]
Type=simple
User=root
Group=root
RuntimeDirectory=mars-nwe-webui
RuntimeDirectoryMode=0700
WorkingDirectory=/
ExecStartPre=/bin/mkdir -p @MARS_NWE_LOG_DIR@
ExecStartPre=/bin/mkdir -p @MARS_NWE_PID_DIR@
ExecStart=@CMAKE_INSTALL_FULL_SBINDIR@/nwwebui -c @MARS_NWE_INSTALL_FULL_CONFDIR@/smart.conf
Restart=on-failure
RestartSec=2
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
ReadWritePaths=@MARS_NWE_INSTALL_FULL_CONFDIR@
ProtectHome=true
[Install]
WantedBy=multi-user.target

1204
nwwebui.c Normal file

File diff suppressed because it is too large Load Diff

BIN
opt/Lable.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 573 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 393 KiB

BIN
opt/logo-small.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 456 KiB

BIN
opt/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

View File

@@ -1,28 +1,12 @@
#
# SMArT
#
#
# Configuration file code
#
#
# Copyright 2001 Wilmer van der Gaast (lintux@lintux.cx)
#
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
#
# Updated for marker-aware config writing.
my( @info, @conf, $l );
my( @info, @conf, @rawconf, $l );
$info[1] = 'Volume';
$info[2] = 'Server name';
@@ -53,6 +37,7 @@ $info[47] = 'Trustee files';
$info[50] = 'Conversion tables';
$info[60] = 'MAX_CONNECTIONS';
$info[61] = 'MAX_NW_VOLS';
$info[62] = 'Reserved';
$info[63] = 'MAX_DIR_BASE_ENTRIES';
$info[68] = 'USE_MMAP';
$info[69] = 'HANDLE_ALL_SAP_TYPS';
@@ -79,19 +64,25 @@ $info[400] = 'nwserv.stations file';
$info[401] = 'Reply to nearest server requests';
$info[402] = 'Reply to connect requests';
open( CONF, '<' . $mars_config );
open( CONF, '<' . $mars_config ) or die "Could not open $mars_config: $!";
@conf = ();
@rawconf = ();
while( $l = <CONF> )
{
$l =~ s/[\r\n]//g;
$l =~ s/[\t ]+/ /g;
$l =~ s/#.*//;
$l =~ s/^[\t ]*//;
$l =~ s/[\t ]*$//;
if( $l ne '' )
{
unshift( @conf, $l );
}
push( @rawconf, $l );
my $x = $l;
$x =~ s/[\r\n]//g;
$x =~ s/[\t ]+/ /g;
$x =~ s/#.*//;
$x =~ s/^[\t ]*//;
$x =~ s/[\t ]*$//;
if( $x ne '' )
{
push( @conf, $x );
}
}
close( CONF );
@@ -99,53 +90,279 @@ sortconfig();
sub sortconfig()
{
@conf = sort( { $a cmp $b } @conf );
@conf = sort( { ( split( ' ', $a ) )[0] <=> ( split( ' ', $b ) )[0] } @conf );
@conf = sort( { $a cmp $b } @conf );
@conf = sort( { ( split( ' ', $a ) )[0] <=> ( split( ' ', $b ) )[0] } @conf );
}
sub getconfigline( $ )
{
my( @c, $c );
@c = getconfig( @_ );
$c = $c[0];
$c =~ s/^[0-9]* //;
return( $c );
my( @c, $c );
@c = getconfig( @_ );
$c = $c[0];
$c =~ s/^[0-9]* //;
return( $c );
}
sub getconfig( $ )
{
my( @c );
@c = grep( /^$_[0] /i, @conf );
return( @c );
my( @c );
@c = grep( /^$_[0] /i, @conf );
return( @c );
}
sub addconfigline( $ )
{
unshift( @conf, $_[0] );
unshift( @conf, $_[0] );
}
sub delconfigline( $ )
{
@conf = grep( !/^$_[0] /i, grep( !/^$_[0]$/i, @conf ) );
@conf = grep( !/^$_[0] /i, grep( !/^$_[0]$/i, @conf ) );
}
sub normalize_line( $ )
{
my $x = $_[0];
$x =~ s/[\r\n]//g;
$x =~ s/[\t ]+/ /g;
$x =~ s/#.*//;
$x =~ s/^[\t ]*//;
$x =~ s/[\t ]*$//;
return( $x );
}
sub section_of_line( $ )
{
my $x = normalize_line( $_[0] );
if( $x =~ /^([0-9]+)\b/ )
{
return( $1 );
}
return( '' );
}
sub grouped_section_key( $ )
{
my $sec = $_[0];
if( $sec >= 100 && $sec <= 106 ) { return '100-106'; }
if( $sec >= 200 && $sec <= 202 ) { return '200-202'; }
if( $sec >= 210 && $sec <= 211 ) { return '210-211'; }
if( $sec >= 300 && $sec <= 302 ) { return '300-302'; }
return $sec;
}
sub build_marker_map()
{
my( %map, $line, $sec, $key );
foreach $line ( @conf )
{
$sec = section_of_line( $line );
next if $sec eq '';
$key = grouped_section_key( $sec );
push( @{ $map{$key} }, $line );
}
# Keep SYS as first entry in section 1
if( defined( $map{'1'} ) )
{
my @sys = grep( /^1 SYS /, @{ $map{'1'} } );
my @rest = grep( !/^1 SYS /, @{ $map{'1'} } );
$map{'1'} = [ @sys, @rest ];
}
return %map;
}
sub writeconfig_compact()
{
my( $i, $l );
sortconfig();
open( CONF, '>' . $mars_config ) or die "Could not write $mars_config: $!";
$l = ( getconfig( '1 SYS' ) )[0];
delconfigline( '1 SYS' );
addconfigline( $l );
foreach $i ( @conf )
{
$l = $i;
$l =~ s/ .*//;
printf( CONF "%-50s # %s\n", $i, $info[$l] );
}
close( CONF );
}
sub writeconfig_markers()
{
my( %secmap, %emitted );
my( $line, $active_key, $inside_active );
%secmap = build_marker_map();
$inside_active = '';
open( CONF, '>' . $mars_config ) or die "Could not write $mars_config: $!";
foreach $line ( @rawconf )
{
if( $line =~ /^\s*#\s*>>>\s*SMARTHOOK\s+SECTION\s+([0-9]+(?:-[0-9]+)?)\s+ACTIVE\s+BEGIN/i )
{
$active_key = $1;
$inside_active = $active_key;
print CONF $line;
if( defined( $secmap{$active_key} ) )
{
foreach my $entry ( @{ $secmap{$active_key} } )
{
print CONF $entry . "\n";
}
}
$emitted{$active_key} = 1;
next;
}
if( $line =~ /^\s*#\s*<<<\s*SMARTHOOK\s+SECTION\s+([0-9]+(?:-[0-9]+)?)\s+ACTIVE\s+END/i )
{
$inside_active = '';
print CONF $line;
next;
}
if( $inside_active ne '' )
{
# Skip old content inside ACTIVE blocks completely.
next;
}
print CONF $line;
}
close( CONF );
}
sub writeconfig_preserve_layout()
{
my( %secmap, %written, @sections, $sec, $line, $sysline );
my( $heading_sec );
sortconfig();
$sysline = ( grep( /^1 SYS /, @conf ) )[0];
if( defined( $sysline ) )
{
@conf = grep( $_ ne $sysline, @conf );
unshift( @conf, $sysline );
}
foreach $line ( @conf )
{
$sec = section_of_line( $line );
if( $sec ne '' )
{
push( @{ $secmap{$sec} }, $line );
}
}
open( CONF, '>' . $mars_config ) or die "Could not write $mars_config: $!";
foreach $line ( @rawconf )
{
if( $line =~ /^\s*#.*Section\s+([0-9]+)\b/i )
{
$heading_sec = $1;
foreach $sec ( sort { $a <=> $b } keys( %secmap ) )
{
next if $written{$sec};
next if $sec > $heading_sec;
if( defined( $secmap{$sec} ) )
{
foreach my $entry ( @{ $secmap{$sec} } )
{
print CONF $entry . "\n";
}
}
$written{$sec} = 1;
}
}
$sec = section_of_line( $line );
if( $sec eq '' )
{
print CONF $line;
next;
}
if( ! $written{$sec} )
{
if( defined( $secmap{$sec} ) )
{
foreach my $entry ( @{ $secmap{$sec} } )
{
print CONF $entry . "\n";
}
}
$written{$sec} = 1;
}
}
@sections = sort { $a <=> $b } keys( %secmap );
foreach $sec ( @sections )
{
next if $written{$sec};
print CONF "\n";
foreach my $entry ( @{ $secmap{$sec} } )
{
print CONF $entry . "\n";
}
$written{$sec} = 1;
}
close( CONF );
}
sub config_has_smart_markers()
{
foreach my $line ( @rawconf )
{
if( $line =~ /SMARTHOOK\s+SECTION/i )
{
return 1;
}
}
return 0;
}
sub writeconfig()
{
my( $i, $l );
sortconfig();
open( CONF, '>' . $mars_config );
$l = ( getconfig( '1 SYS' ) )[0];
delconfigline( '1 SYS' );
addconfigline( $l );
foreach $i ( @conf )
{
$l = $i;
$l =~ s/ .*//;
printf( CONF "%-50s # %s\n", $i, $info[$l] );
}
close( CONF );
if( defined( $smart_compact_nwservconf ) && $smart_compact_nwservconf )
{
writeconfig_compact();
}
elsif( config_has_smart_markers() )
{
writeconfig_markers();
}
else
{
writeconfig_preserve_layout();
}
}

File diff suppressed because it is too large Load Diff

310
smart
View File

@@ -1,310 +0,0 @@
#!/usr/bin/perl
#
# SMArT
#
# Main program file
#
# Copyright 2001 Wilmer van der Gaast
#
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
#
$redirected = 0;
$server_id = 'Server: SMArT/Perl/0.99';
$base = $0;
$base =~ s/\/[^\/]*$//g;
$base .= '/';
do( $base . 'smart.conf' );
close( STDERR );
open( STDERR, '>>' . $base . 'error.log' );
$ENV{HOME} = $base;
$l = <STDIN>;
$l =~ s/[\n\r]//g;
@c = split( ' ', $l );
if( scalar( @c ) > 2 )
{
while( keys( %h ) < 15 ) # Who would ever want to send more headers???
{
$l = <STDIN>;
$l =~ s/[\n\r]//g;
if( $l eq '' )
{ last; }
$n = $l;
$n =~ s/:[^:]*$//g;
$v = $l;
$v =~ s/^[^:]*://g;
$h{$n} = $v;
}
}
$c[0] = uc( $c[0] );
if( $h{Authorization} eq '' )
{ error( 401 ); }
else
{
@s = split( ' ', $h{Authorization} );
if( $s[0] ne 'Basic' or length( $h{Authorization} ) > 80 ) # We can't be too careful, can we...
{ error( 401 ); }
else
{
$s[1] =~ tr#A-Za-z0-9+/##cd;
$s[1] =~ tr#A-Za-z0-9+/# -_#;
$s[1] = pack( 'c', 32 + 0.75 * length( $s[1] ) ) . $s[1];
$s[1] = unpack( 'u', $s[1] );
$s[1] =~ s/[\r\n]//g;
@l = split( ':', $s[1] );
if( $l[0] ne 'root' )
{ error( 401 ); }
else
{ if( $x = system( $base . 'check_login', @l ) )
{ error( 401 ); } }
}
}
if( $c[0] ne 'GET' )
{
error( 501 );
}
@p = split( '\?', $c[1] );
$cc = $c[1];
$cc =~ s/[^\?]*\?//;
$c = substr( shift( @p ), 1 );
@p = split( '&', $p[0] );
foreach $p ( @p )
{
$n = $p;
$n =~ s/=.*//;
$v = $p;
$v =~ s/.*=//;
$v =~ s/\+/ /g;
$v =~ s/%([0-9A-F][0-9A-F])/pack('c',hex($1))/gie;
$p{$n} = $v;
}
@c = split( '/', $c );
if( $c[0] eq 'apply' )
{
do( $base . 'readconfig.pl' );
do( $base . 'apply.pl' );
handle_request();
exit;
}
elsif( $c[0] eq 'settings' )
{
do( $base . 'readconfig.pl' );
}
drop_root();
if( $c[0] eq '' )
{
print <<EOF;
HTTP/1.0 200 OK
Content-Type: text/html
$server_id
<HTML>
<HEAD>
<TITLE>SMArT</TITLE>
</HEAD>
<FRAMESET COLS="50%,50%">
<FRAME NAME="MENU" SRC="/static/menu.html">
<FRAME NAME="OPTS" SRC="/static/start.html">
</FRAMESET>
</HTML>
EOF
exit;
}
elsif( $c[0] eq 'static' )
{
do( $base . 'static.pl' );
}
elsif( $c[0] eq 'settings' )
{
do( $base . 'settings.pl' );
}
else
{
error( 500 );
}
handle_request();
exit;
##########################################
##### END OF MAIN PROCEDURES FOLLOW #####
##########################################
sub error( $ )
{
if( $_[0] eq '401' )
{
print <<EOF;
HTTP/1.0 401 Access denied
Content-Type: text/plain
WWW-Authenticate: Basic realm="SMArT"
$server_id
You're not allowed to access this URL.
EOF
}
elsif( $_[0] eq '404' )
{
print <<EOF;
HTTP/1.0 404 File not found
Content-Type: text/plain
$server_id
The file you requested does not exist.
EOF
}
elsif( $_[0] eq '501' )
{
print <<EOF;
HTTP/1.0 501 Unknown command
Content-Type: text/plain
$server_id
Unknown command: $c[0]
EOF
}
else
{
print <<EOF;
HTTP/1.0 500 Internal server error
Content-Type: text/plain
$server_id
Something went wrong...
EOF
}
exit;
}
sub unix_userlist()
{
my( @c, @d, %e );
while( @d = getpwent )
{
unshift( @c, { name => $d[0], uid => $d[2] } );
}
return( sort( { $a->{"name"} cmp $b->{"name"} } @c ) );
}
sub unix_grouplist()
{
my( @c, @d, %e );
while( @d = getgrent )
{
unshift( @c, { name => $d[0], gid => $d[2] } );
}
return( sort( { $a->{"name"} cmp $b->{"name"} } @c ) );
}
sub redirect( $ )
{
if( $redirected != 0 )
{ return( 0 ); }
$redirected = 1;
print <<EOF;
HTTP/1.0 302 Page relocated
Content-Type: text/plain
Location: $_[0]
$server_id
EOF
}
sub get_server
{
open( SFILE, '<' . $base . '.nwclient' );
chomp( $line = <SFILE> );
close( SFILE );
$line =~ s/\/.*//;
return( $line );
}
sub get_bindery_password
{
open( SFILE, '<' . $base . '.nwclient' );
chomp( $line = <SFILE> );
close( SFILE );
$line =~ s/.* //;
return( $line );
}
sub read_property_string
{
my @x = split( "\n", `nwbpvalues -c -o $_[0] -t $_[1] -p $_[2] -S $server` );
my( $i, $s );
$i = 5;
while( hex( $x[$i] ) > 0 )
{
$s .= pack( 'c', hex( $x[$i] ) );
$i ++;
}
return( $s );
}
sub read_property_list
{
my @x = split( "\n", `nwbpvalues -c -o $_[0] -t $_[1] -p $_[2] -S $server` );
my( $i, @l );
$i = 6;
while( $x[$i] ne '' )
{
unshift( @l, $x[$i] );
$i += 2;
}
return( @l );
}
sub write_property_string
{
open( FILE, '|' . 'nwbpset -S ' . $server );
print FILE <<EOF;
000$_[1]
$_[0]
$_[2]
$_[3]
$_[4]
EOF
for $i ( 0 .. length( $_[5] ) - 1 )
{
print( FILE unpack( 'H2', substr( $_[5], $i, 1 ) ) . "\n" );
}
close( FILE );
}
sub drop_root
{
$< = $> = getpwnam( $nonroot_user );
}

707
smart.cmake Normal file
View File

@@ -0,0 +1,707 @@
#!/usr/bin/perl
#
# SMArT
#
# Main program file
#
# Copyright 2001 Wilmer van der Gaast
#
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
#
$redirected = 0;
$server_id = 'Server: SMArT/Perl/@MARS_NWE_VERSION@';
do( '@MARS_NWE_INSTALL_FULL_CONFDIR@/smart.conf' )
or die "Could not load @MARS_NWE_INSTALL_FULL_CONFDIR@/smart.conf: $@ $!";
close( STDERR );
# Prefix all raw STDERR from helper tools with timestamp/component before it
# reaches smart.log. This also catches output from nwbols/nwbpset/nwpasswd
# and systemctl warnings.
my $smart_stderr_filter = "perl -MPOSIX=strftime -ne 'chomp; " .
"my \\$v=\\$ENV{SMART_VERSION}||q{0.99.pl28}; " .
"my \\$f=\\$ENV{SMART_LOG_FILE}||q{stderr}; " .
"print strftime(q{[%Y-%m-%d %H:%M:%S]}, localtime), qq{ [ERROR] [SMArT \\$v] [\\$f] \\$_\\n};' >> " .
quotemeta( $smart_log_path );
$ENV{SMART_VERSION} = defined( $smart_version ) && $smart_version ne '' ? $smart_version : '0.99.pl28';
$ENV{SMART_LOG_FILE} = 'stderr';
open( STDERR, '|-', $smart_stderr_filter )
or open( STDERR, '>>' . $smart_log_path )
or die "Could not open $smart_log_path: $!";
$ENV{HOME} = '@MARS_NWE_INSTALL_FULL_CONFDIR@';
$smart_libexec_dir = '@MARS_NWE_INSTALL_FULL_LIBEXECDIR@';
$smart_libexec_dir =~ s#/*$##;
$smart_control_path = $smart_libexec_dir . '/control' unless defined $smart_control_path;
$mars_nwe_service = '@MARS_NWE_SYSTEMD_SERVICE@' unless defined $mars_nwe_service;
$smart_systemctl_path = '@SYSTEMCTL_EXECUTABLE@' unless defined $smart_systemctl_path;
$l = <STDIN>;
$l =~ s/[\n\r]//g;
$request_uri = "";
$post_body = "";
%hl = ();
@c = split( ' ', $l );
if( scalar( @c ) > 2 )
{
$request_uri = $c[1];
while( keys( %h ) < 50 )
{
$l = <STDIN>;
$l =~ s/[\n\r]//g;
if( $l eq '' )
{ last; }
$n = $l;
$n =~ s/:[^:]*$//g;
$v = $l;
$v =~ s/^[^:]*://g;
$v =~ s/^\s+//;
$v =~ s/\s+$//;
$h{$n} = $v;
$hl{lc( $n )} = $v;
}
}
$c[0] = uc( $c[0] );
$request_method = $c[0];
if( $request_method eq 'POST' )
{
my $content_length = 0;
if( defined( $hl{'content-length'} ) && $hl{'content-length'} =~ /^[0-9]+$/ )
{
$content_length = int( $hl{'content-length'} );
}
if( $content_length > 0 && $content_length < 8192 )
{
read( STDIN, $post_body, $content_length );
}
}
if( $request_method ne 'GET' && $request_method ne 'POST' )
{
error( 501 );
}
@p = split( '\?', $c[1] );
$cc = $c[1];
$cc =~ s/[^\?]*\?//;
$c = substr( shift( @p ), 1 );
parse_params( $p[0] );
parse_params( $post_body ) if $request_method eq 'POST';
@c = split( '/', $c );
if( $c[0] eq 'login' )
{
handle_login_route();
exit;
}
if( $c[0] eq 'logout' )
{
handle_logout_route();
exit;
}
if( ! valid_session() )
{
redirect( '/login' );
exit;
}
if( ( $c[0] eq 'service' && $c[1] eq 'control' ) ||
( $c[0] eq 'cgi-bin' && $c[1] eq 'control' ) )
{
# Service control must run before drop_root().
# /service/control is the preferred path; /cgi-bin/control is kept as a legacy alias.
my $rv = do( $smart_control_path );
if( ! defined $rv )
{
error( 500 );
}
handle_request();
exit;
}
elsif( $c[0] eq 'apply' )
{
do( $smart_libexec_dir . '/readconfig.pl' );
do( $smart_libexec_dir . '/apply.pl' );
handle_request();
exit;
}
elsif( $c[0] eq 'settings' )
{
do( $smart_libexec_dir . '/readconfig.pl' );
}
drop_root();
if( $c[0] eq '' )
{
print <<EOF;
HTTP/1.0 200 OK
Content-Type: text/html
$server_id
<HTML>
<HEAD>
<TITLE>SMArT</TITLE>
</HEAD>
<FRAMESET COLS="50%,50%">
<FRAME NAME="MENU" SRC="/static/menu.html">
<FRAME NAME="OPTS" SRC="/static/start.html">
</FRAMESET>
</HTML>
EOF
exit;
}
elsif( $c[0] eq 'static' )
{
do( $smart_libexec_dir . '/static.pl' );
}
elsif( $c[0] eq 'settings' )
{
do( $smart_libexec_dir . '/settings.pl' );
}
else
{
error( 500 );
}
handle_request();
exit;
##########################################
##### END OF MAIN PROCEDURES FOLLOW #####
##########################################
sub smart_log_line( $$$ )
{
my( $level, $file, $msg ) = @_;
$level = 'INFO' unless defined( $level ) && $level ne '';
$file = 'smart' unless defined( $file ) && $file ne '';
$msg = '' unless defined( $msg );
my( $sec, $min, $hour, $mday, $mon, $year ) = localtime( time() );
my $ts = sprintf( "%04d-%02d-%02d %02d:%02d:%02d",
$year + 1900, $mon + 1, $mday, $hour, $min, $sec );
my $version = defined( $smart_version ) && $smart_version ne '' ? $smart_version : '0.99.pl28';
if( open( my $fh, '>>', $smart_log_path ) )
{
print( $fh '[' . $ts . '] [' . $level . '] [SMArT ' . $version . '] [' . $file . '] ' . $msg . "\n" );
close( $fh );
}
}
sub smart_auth_log( $ )
{
my $msg = $_[0];
$msg = '' unless defined $msg;
my( $sec, $min, $hour, $mday, $mon, $year ) = localtime( time() );
my $ts = sprintf( "%04d-%02d-%02d %02d:%02d:%02d",
$year + 1900, $mon + 1, $mday, $hour, $min, $sec );
my $version = defined( $smart_version ) && $smart_version ne '' ? $smart_version : '0.99.pl28';
if( open( my $fh, '>>', $smart_log_path ) )
{
print( $fh '[' . $ts . '] [INFO] [SMArT ' . $version . '] [smart] ' . $msg . "\n" );
close( $fh );
}
}
sub parse_params( $ )
{
my $qs = $_[0];
return if ! defined( $qs ) || $qs eq '';
my @items = split( '&', $qs );
foreach my $item ( @items )
{
my $n = $item;
my $v = $item;
$n =~ s/=.*//;
$v =~ s/^[^=]*=?//;
$n =~ s/\+/ /g;
$v =~ s/\+/ /g;
$n =~ s/%([0-9A-Fa-f][0-9A-Fa-f])/pack('c',hex($1))/gie;
$v =~ s/%([0-9A-Fa-f][0-9A-Fa-f])/pack('c',hex($1))/gie;
$p{$n} = $v;
}
}
sub smart_html_escape( $ )
{
my $s = $_[0];
$s = '' unless defined $s;
$s =~ s/&/&amp;/g;
$s =~ s/</&lt;/g;
$s =~ s/>/&gt;/g;
$s =~ s/"/&quot;/g;
return $s;
}
sub session_timeout()
{
return $smart_session_timeout if defined( $smart_session_timeout ) && $smart_session_timeout =~ /^[0-9]+$/ && $smart_session_timeout > 0;
return 3600;
}
sub session_dir()
{
my $dir = defined( $smart_session_dir ) && $smart_session_dir ne '' ? $smart_session_dir : '/run/mars-nwe-webui';
if( ! -d $dir )
{
if( ! mkdir( $dir, 0700 ) )
{
smart_auth_log( 'could not create session dir ' . $dir . ': ' . $! );
}
}
if( -d $dir )
{
chmod( 0700, $dir );
}
else
{
smart_auth_log( 'session dir is not available: ' . $dir );
}
return $dir;
}
sub session_token()
{
my $token = '';
if( open( my $fh, '<', '/dev/urandom' ) )
{
my $buf = '';
read( $fh, $buf, 24 );
close( $fh );
$token = unpack( 'H*', $buf );
}
if( $token eq '' )
{
$token = sprintf( "%08x%08x%08x%08x", time(), $$, int( rand( 0xffffffff ) ), int( rand( 0xffffffff ) ) );
}
$token =~ s/[^A-Fa-f0-9]//g;
return $token;
}
sub session_file( $ )
{
my $token = $_[0];
$token = '' unless defined $token;
$token =~ s/[^A-Fa-f0-9]//g;
return '' if $token eq '';
return session_dir() . '/' . $token;
}
sub cookie_session_id()
{
my $cookie = defined( $hl{'cookie'} ) ? $hl{'cookie'} : '';
foreach my $part ( split( /;/, $cookie ) )
{
$part =~ s/^\s+//;
$part =~ s/\s+$//;
if( $part =~ /^SMArT_SID=([A-Fa-f0-9]+)$/ )
{
return $1;
}
}
return '';
}
sub valid_session()
{
my $token = cookie_session_id();
my $file = session_file( $token );
return 0 if $token eq '';
return 0 if $file eq '';
if( ! -f $file )
{
smart_auth_log( 'session cookie exists but file is missing: ' . $file );
return 0;
}
my @st = stat( $file );
if( scalar( @st ) == 0 )
{
smart_auth_log( 'could not stat session file: ' . $file );
return 0;
}
if( time() - $st[9] > session_timeout() )
{
unlink( $file );
smart_auth_log( 'session expired: ' . $file );
return 0;
}
utime( time(), time(), $file );
return 1;
}
sub create_session( $ )
{
my $user = $_[0];
my $token = session_token();
my $file = session_file( $token );
if( $file eq '' )
{
smart_auth_log( 'could not build session file path' );
return '';
}
if( open( my $fh, '>', $file ) )
{
print( $fh $user . "\n" . time() . "\n" );
close( $fh );
chmod( 0600, $file );
smart_auth_log( 'created session for ' . $user . ' at ' . $file );
return $token;
}
smart_auth_log( 'could not create session file ' . $file . ': ' . $! );
return '';
}
sub destroy_session()
{
my $token = cookie_session_id();
my $file = session_file( $token );
unlink( $file ) if $file ne '' && -f $file;
}
sub check_login_password( $$ )
{
my( $user, $pass ) = @_;
return 0 if ! defined( $user ) || ! defined( $pass );
return 0 if $user ne 'root';
if( ! defined( $smart_check_login ) || $smart_check_login eq '' || ! -x $smart_check_login )
{
return -1;
}
return system( $smart_check_login, $user, $pass ) == 0 ? 1 : 0;
}
sub print_login_page( $ )
{
my $msg = smart_html_escape( $_[0] );
print <<EOF;
HTTP/1.0 200 OK
Content-Type: text/html
$server_id
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>SMArT Login</title>
<style>
:root{--bg:#f4f1ea;--panel:#faf8f4;--line:#dfd2bf;--text:#3d342c;--muted:#6f6257;--accent:#ad1d1c;--gold:#b9813d}
*{box-sizing:border-box}
html,body{margin:0;padding:0;min-height:100%;background:var(--bg);color:var(--text);font:15px/1.5 Arial,Helvetica,sans-serif}
body{display:flex;align-items:center;justify-content:center;padding:24px}
.login{width:min(440px,100%);background:var(--panel);border:1px solid var(--line);border-radius:20px;box-shadow:0 18px 45px rgba(64,36,12,.12);overflow:hidden}
.hero{padding:26px 28px;background:linear-gradient(135deg,#a80f18,#c44731 60%,#d79a54);color:white}
.hero{display:flex;align-items:center;gap:16px}.hero img{width:54px;height:auto;background:#fff;border-radius:12px;padding:6px;box-shadow:0 8px 20px rgba(0,0,0,.16)}.hero h1{margin:0;font-size:28px}
.hero p{margin:6px 0 0;opacity:.95}
form{padding:24px 28px 28px}
label{display:block;font-weight:bold;margin:0 0 7px}
input{width:100%;border:1px solid #cdbb9f;border-radius:12px;padding:10px 12px;background:#fffdf9;color:var(--text);font-size:15px;margin:0 0 16px}
button{width:100%;border:1px solid #a33d2f;border-radius:12px;padding:11px 14px;background:#b84434;color:#fff;font-weight:bold;font-size:15px;cursor:pointer}
.msg{margin:0 0 16px;padding:10px 12px;border-radius:12px;background:#fff3e0;border:1px solid #ead0a4;color:#7a3d18}
.note{margin-top:14px;color:var(--muted);font-size:13px;text-align:center}
</style>
</head>
<body>
<div class="login">
<div class="hero">
<img src="/static/smart_icon.jpg" alt="SMArT logo">
<div>
<h1>SMArT Login</h1>
<p>MARS_NWE web administration</p>
</div>
</div>
<form method="POST" action="/login">
EOF
if( $msg ne '' )
{
print '<div class="msg">' . $msg . "</div>\n";
}
print <<EOF;
<label for="user">User</label>
<input id="user" name="user" value="root" autocomplete="username">
<label for="pass">Password</label>
<input id="pass" name="pass" type="password" autocomplete="current-password" autofocus>
<button type="submit">Login</button>
</form>
</div>
</body>
</html>
EOF
}
sub handle_login_route()
{
if( $request_method ne 'POST' )
{
print_login_page( '' );
return;
}
my $rv = check_login_password( $p{user}, $p{pass} );
if( $rv == -1 )
{
print_login_page( 'Login helper check_login is missing or not executable.' );
return;
}
if( $rv != 1 )
{
print_login_page( 'Login failed.' );
return;
}
my $token = create_session( $p{user} );
if( $token eq '' )
{
print_login_page( 'Could not create login session.' );
return;
}
print <<EOF;
HTTP/1.0 302 Found
Location: /
Set-Cookie: SMArT_SID=$token; Path=/; HttpOnly; Max-Age=3600
$server_id
EOF
}
sub handle_logout_route()
{
destroy_session();
print <<EOF;
HTTP/1.0 302 Found
Location: /login
Set-Cookie: SMArT_SID=deleted; Path=/; HttpOnly; Max-Age=0
$server_id
EOF
}
sub error( $ )
{
if( $_[0] eq '401' )
{
print <<EOF;
HTTP/1.0 401 Access denied
Content-Type: text/plain
WWW-Authenticate: Basic realm="SMArT"
$server_id
You're not allowed to access this URL.
EOF
}
elsif( $_[0] eq '404' )
{
print <<EOF;
HTTP/1.0 404 File not found
Content-Type: text/plain
$server_id
The file you requested does not exist.
EOF
}
elsif( $_[0] eq '501' )
{
print <<EOF;
HTTP/1.0 501 Unknown command
Content-Type: text/plain
$server_id
Unknown command: $c[0]
EOF
}
else
{
print <<EOF;
HTTP/1.0 500 Internal server error
Content-Type: text/plain
$server_id
Something went wrong...
EOF
}
exit;
}
sub unix_userlist()
{
my( @c, @d, %e );
while( @d = getpwent )
{
unshift( @c, { name => $d[0], uid => $d[2] } );
}
return( sort( { $a->{"name"} cmp $b->{"name"} } @c ) );
}
sub unix_grouplist()
{
my( @c, @d, %e );
while( @d = getgrent )
{
unshift( @c, { name => $d[0], gid => $d[2] } );
}
return( sort( { $a->{"name"} cmp $b->{"name"} } @c ) );
}
sub redirect( $ )
{
if( $redirected != 0 )
{ return( 0 ); }
$redirected = 1;
print <<EOF;
HTTP/1.0 302 Page relocated
Content-Type: text/plain
Location: $_[0]
$server_id
EOF
}
sub get_server
{
open( SFILE, '<' . $smart_nwclient_path );
chomp( $line = <SFILE> );
close( SFILE );
$line =~ s/\/.*//;
return( $line );
}
sub get_bindery_password
{
open( SFILE, '<' . $smart_nwclient_path );
chomp( $line = <SFILE> );
close( SFILE );
$line =~ s/.* //;
return( $line );
}
sub read_property_string
{
my @x = split( "\n", `nwbpvalues -c -o $_[0] -t $_[1] -p $_[2] -S $server` );
my( $i, $s );
$i = 5;
while( hex( $x[$i] ) > 0 )
{
$s .= pack( 'c', hex( $x[$i] ) );
$i ++;
}
return( $s );
}
sub read_property_list
{
my @x = split( "\n", `nwbpvalues -c -o $_[0] -t $_[1] -p $_[2] -S $server` );
my( $i, @l );
$i = 6;
while( $x[$i] ne '' )
{
unshift( @l, $x[$i] );
$i += 2;
}
return( @l );
}
sub write_property_string
{
open( FILE, '|' . 'nwbpset -S ' . $server );
print FILE <<EOF;
000$_[1]
$_[0]
$_[2]
$_[3]
$_[4]
EOF
for $i ( 0 .. length( $_[5] ) - 1 )
{
print( FILE unpack( 'H2', substr( $_[5], $i, 1 ) ) . "\n" );
}
close( FILE );
}
sub drop_root
{
$< = $> = getpwnam( $nonroot_user );
}

View File

@@ -1,11 +0,0 @@
$COLOR_BACK = "#F0F0FF";
$COLOR_HEAD_BACK = "#C0C0FF";
$COLOR_HEAD_FORE = "#000000";
$COLOR_SUBH_BACK = "#D0D0FF";
$COLOR_SUBH_FORE = "#000000";
$COLOR_TEXT_BACK = "#E0E0FF";
$COLOR_TEXT_FORE = "#000000";
$mars_config = '/usr/local/etc/nwserv.conf';
$nonroot_user = 'nobody';

152
smart.conf.cmake Normal file
View File

@@ -0,0 +1,152 @@
# SMArT / nwwebui configuration file
# ------------------------------------------------------------
# UI colors
# ------------------------------------------------------------
# Background color used for the main page body.
$COLOR_BACK = "#F0F0FF";
# Background color used for section headers.
$COLOR_HEAD_BACK = "#C0C0FF";
# Text color used for section headers.
$COLOR_HEAD_FORE = "#000000";
# Background color used for subsection headers.
$COLOR_SUBH_BACK = "#D0D0FF";
# Text color used for subsection headers.
$COLOR_SUBH_FORE = "#000000";
# Background color used for normal content rows.
$COLOR_TEXT_BACK = "#E0E0FF";
# Text color used for normal content rows.
$COLOR_TEXT_FORE = "#000000";
# ------------------------------------------------------------
# Main MARS NWE configuration
# ------------------------------------------------------------
# Path to the main mars_nwe server configuration file.
# This file is read and modified by the SMArT configuration pages.
$mars_config = '@MARS_NWE_INSTALL_FULL_CONFDIR@/nwserv.conf';
# User name used when SMArT drops privileges for non-root operations.
# Keep this set to an unprivileged local account.
$nonroot_user = 'nobody';
# Write the mars_nwe configuration file in compact form.
# 0 = preserve comments, blank lines and the original section layout
# 1 = write a compact configuration file without the original long comments
$smart_compact_nwservconf = 0;
# ------------------------------------------------------------
# SMArT internal file layout
# ------------------------------------------------------------
# Absolute path to the SMArT configuration file itself.
# Used when SMArT needs to append updated settings.
$smart_conf_path = '@MARS_NWE_INSTALL_FULL_CONFDIR@/smart.conf';
# File used to store bindery login information for SMArT helper tools.
# This file should remain readable only by the service user or root.
$smart_nwclient_path = '@MARS_NWE_INSTALL_FULL_CONFDIR@/.nwclient';
# Directory containing static HTML and image files served by SMArT.
$smart_static_dir = '@MARS_NWE_INSTALL_FULL_LIBEXECDIR@/static';
# Log file used by the Perl SMArT frontend.
# Keep this separate from the nwwebui log file.
$smart_log_path = '@MARS_NWE_LOG_DIR@/smart.log';
# Path to the PAM-based login helper used for root authentication.
$smart_check_login = '@MARS_NWE_INSTALL_FULL_LIBEXECDIR@/check_login';
# Path to the SMArT service-control helper.
$smart_control_path = '@MARS_NWE_INSTALL_FULL_LIBEXECDIR@/control';
# systemd unit controlled by the SMArT service-control page.
$mars_nwe_service = '@MARS_NWE_SYSTEMD_SERVICE@';
# systemctl executable used by the service-control helper.
$smart_systemctl_path = '@SYSTEMCTL_EXECUTABLE@';
# Optional explicit path to the main SMArT Perl program.
# This is normally not required, because nwwebui already has a built-in default.
# Uncomment and adjust only if a non-standard location must be used.
# $smart_perl_path = '@MARS_NWE_INSTALL_FULL_LIBEXECDIR@/smart';
# ------------------------------------------------------------
# Host printer / CUPS utility integration
# ------------------------------------------------------------
# Enable CUPS host-printer discovery in the SMArT queue UI.
# This value is generated from cmake/modules/cupsutils.cmake.
# 1 = enabled, 0 = disabled
$smart_cups_enable = '@SMART_CUPS_ENABLE@';
# lpstat executable used to discover local CUPS printers for the queue UI.
$smart_cups_lpstat_path = '@CUPS_LPSTAT_EXECUTABLE@';
# Command template used when SMArT creates a print queue from a CUPS printer.
# %p is replaced with the sanitized CUPS printer name.
# The trailing '-' makes lp read the print job from stdin.
$smart_cups_print_command_template = '@CUPS_LP_EXECUTABLE@ -d %p -';
# ------------------------------------------------------------
# nwwebui listener settings
# ------------------------------------------------------------
# IP address used for the HTTP and HTTPS listeners.
# Use 0.0.0.0 to listen on all IPv4 interfaces.
# Use 127.0.0.1 for local-only testing.
$nw_bind_ip = '0.0.0.0';
# Log level used by nwwebui.
# 0 = errors only
# 1 = informational messages
# 2 = debug messages
$nw_log_level = 1;
# Run nwwebui in daemon mode by default.
# 0 = stay in foreground
# 1 = detach into background
$nw_daemonize = 0;
# PID file written by nwwebui.
$nw_pid_file = '@MARS_NWE_PID_DIR@/nwwebui.pid';
# Log file written by nwwebui.
$nw_log_file = '@MARS_NWE_LOG_DIR@/nwwebui.log';
# Enable or disable TLS/SSL support.
# 1 = enable HTTPS listener
# 0 = disable HTTPS listener
#
# When disabled, nwwebui can still serve plain HTTP if nw_http_port > 0.
$nw_ssl_enable = 0;
# Plain HTTP listener port.
# Set to 0 to disable plain HTTP completely.
# This is useful for local or isolated-network testing.
$nw_http_port = 9080;
# HTTPS listener port.
# Used only when $nw_ssl_enable is set to 1.
# Set to 0 to disable HTTPS listening.
$nw_https_port = 9443;
# TLS certificate file in PEM format.
# Required only when HTTPS is enabled.
$nw_cert_file = '@MARS_NWE_INSTALL_FULL_CONFDIR@/server.crt';
# TLS private key file in PEM format.
# Required only when HTTPS is enabled.
$nw_key_file = '@MARS_NWE_INSTALL_FULL_CONFDIR@/server.key';
# Directory for HTML login cookie sessions. Created by systemd RuntimeDirectory.
$smart_session_dir = '/run/mars-nwe-webui';
$smart_session_timeout = 3600;

4
smart.pamd Normal file
View File

@@ -0,0 +1,4 @@
auth required pam_unix.so
account required pam_unix.so
password required pam_unix.so
session required pam_unix.so

171
smart_userlist.c Normal file
View File

@@ -0,0 +1,171 @@
/*
SMArT
List local/NSS users for the WebUI.
PAM itself cannot enumerate users. User enumeration is done through NSS
getpwent(), so /etc/nsswitch.conf is honored (files, sss, ldap, nis, ...).
Optionally, each user can be checked with pam_acct_mgmt() against the "smart"
PAM service.
Output format:
username<TAB>uid<TAB>gid<TAB>gecos<TAB>home<TAB>shell
*/
#include <errno.h>
#include <pwd.h>
#include <security/pam_appl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
static int empty_conv(int num_msg, const struct pam_message **msg,
struct pam_response **resp, void *appdata_ptr)
{
struct pam_response *reply;
(void) msg;
(void) appdata_ptr;
if (num_msg <= 0) {
*resp = NULL;
return PAM_SUCCESS;
}
reply = calloc((size_t) num_msg, sizeof(struct pam_response));
if (reply == NULL) {
return PAM_BUF_ERR;
}
*resp = reply;
return PAM_SUCCESS;
}
static int pam_account_ok(const char *service, const char *user)
{
struct pam_conv conv = { empty_conv, NULL };
pam_handle_t *pamh = NULL;
int rc;
rc = pam_start(service, user, &conv, &pamh);
if (rc == PAM_SUCCESS) {
rc = pam_acct_mgmt(pamh, PAM_SILENT);
}
if (pamh != NULL) {
pam_end(pamh, rc);
}
return rc == PAM_SUCCESS;
}
static void print_sanitized(const char *s)
{
const unsigned char *p = (const unsigned char *) (s != NULL ? s : "");
while (*p != '\0') {
if (*p == '\t' || *p == '\n' || *p == '\r') {
putchar(' ');
} else {
putchar((int) *p);
}
p++;
}
}
static int is_safe_name(const char *s)
{
const unsigned char *p = (const unsigned char *) s;
if (s == NULL || *s == '\0') {
return 0;
}
while (*p != '\0') {
if (!( (*p >= 'A' && *p <= 'Z') ||
(*p >= 'a' && *p <= 'z') ||
(*p >= '0' && *p <= '9') ||
*p == '_' || *p == '-' || *p == '.' )) {
return 0;
}
p++;
}
return 1;
}
int main(int argc, char **argv)
{
struct passwd *pw;
uid_t min_uid = 1000;
int include_system = 0;
int pam_check = 0;
const char *pam_service = "smart";
int i;
for (i = 1; i < argc; i++) {
if (strcmp(argv[i], "--all") == 0) {
include_system = 1;
min_uid = 0;
} else if (strcmp(argv[i], "--min-uid") == 0 && i + 1 < argc) {
char *end = NULL;
unsigned long v = strtoul(argv[++i], &end, 10);
if (end == NULL || *end != '\0') {
fprintf(stderr, "Invalid --min-uid value\n");
return 2;
}
min_uid = (uid_t) v;
} else if (strcmp(argv[i], "--pam-check") == 0) {
pam_check = 1;
} else if (strcmp(argv[i], "--pam-service") == 0 && i + 1 < argc) {
pam_service = argv[++i];
} else {
fprintf(stderr,
"Usage: %s [--all] [--min-uid UID] [--pam-check] [--pam-service SERVICE]\n",
argv[0]);
return 2;
}
}
errno = 0;
setpwent();
while ((pw = getpwent()) != NULL) {
if (!is_safe_name(pw->pw_name)) {
continue;
}
if (!include_system && pw->pw_uid < min_uid) {
continue;
}
if (!include_system &&
(strcmp(pw->pw_name, "root") == 0 || strcmp(pw->pw_name, "nobody") == 0)) {
continue;
}
if (pam_check && !pam_account_ok(pam_service, pw->pw_name)) {
continue;
}
print_sanitized(pw->pw_name);
printf("\t%lu\t%lu\t", (unsigned long) pw->pw_uid, (unsigned long) pw->pw_gid);
print_sanitized(pw->pw_gecos);
putchar('\t');
print_sanitized(pw->pw_dir);
putchar('\t');
print_sanitized(pw->pw_shell);
putchar('\n');
}
endpwent();
if (errno != 0) {
perror("getpwent");
return 1;
}
return 0;
}

View File

@@ -5,33 +5,20 @@
#
# Copyright 2001 Wilmer van der Gaast (lintux@lintux.cx)
#
# Updated with favicon and modern asset MIME type support.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
#
sub handle_request()
{
$f = $c[1];
$f =~ s/[^-_\.A-Za-z0-9]//g;
if( ! open( FILE, $base . 'static/' . $f ) )
if( ! open( FILE, $smart_static_dir . '/' . $f ) )
{
error( 404 );
}
$e = $f;
$e =~ s/^.*\.//;
$e = lc( $e );
if( $e eq 'html' )
{
$t = 'text/html';
@@ -40,22 +27,48 @@ sub handle_request()
{
$t = 'image/gif';
}
elsif( $e eq 'jpg' )
elsif( $e eq 'jpg' || $e eq 'jpeg' )
{
$t = 'image/jpeg';
}
elsif( $e eq 'png' )
{
$t = 'image/png';
}
elsif( $e eq 'ico' )
{
$t = 'image/x-icon';
}
elsif( $e eq 'svg' )
{
$t = 'image/svg+xml';
}
elsif( $e eq 'webp' )
{
$t = 'image/webp';
}
elsif( $e eq 'css' )
{
$t = 'text/css';
}
elsif( $e eq 'js' )
{
$t = 'application/javascript';
}
else
{
$t = 'application/octet-stream';
}
print <<EOF;
print <<EOF2;
HTTP/1.0 200 OK
Content-Type: $t
$server_id
EOF
while( <FILE> )
EOF2
binmode( FILE );
while( read( FILE, $buf, 8192 ) )
{
print;
print $buf;
}
close( FILE );
}

BIN
static/apple-touch-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

BIN
static/favicon-16x16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 973 B

BIN
static/favicon-32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

BIN
static/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

17
static/icon-configh.svg Normal file
View File

@@ -0,0 +1,17 @@
<svg xmlns="http://www.w3.org/2000/svg" width="96" height="96" viewBox="0 0 100 100" role="img" aria-hidden="true">
<style>
.icon-bg{fill:url(#g);stroke:#d6bf9c;stroke-width:1.8}
.icon-shape{fill:none;stroke:#ad1d1c;stroke-width:5.2;stroke-linecap:round;stroke-linejoin:round}
</style>
<defs>
<linearGradient id="g" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#fffaf2"/>
<stop offset="1" stop-color="#ead9bd"/>
</linearGradient>
</defs>
<rect class="icon-bg" x="6" y="6" width="88" height="88" rx="20"/>
<g class="icon-shape" color="#ad1d1c">
<path d="M42 16h16l3 13 12-7 8 14-10 8 10 8-8 14-12-7-3 13H42l-3-13-12 7-8-14 10-8-10-8 8-14 12 7z"/>
<circle cx="50" cy="50" r="12"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 704 B

19
static/icon-devices.svg Normal file
View File

@@ -0,0 +1,19 @@
<svg xmlns="http://www.w3.org/2000/svg" width="96" height="96" viewBox="0 0 100 100" role="img" aria-hidden="true">
<style>
.icon-bg{fill:url(#g);stroke:#d6bf9c;stroke-width:1.8}
.icon-shape{fill:none;stroke:#ad1d1c;stroke-width:5.2;stroke-linecap:round;stroke-linejoin:round}
</style>
<defs>
<linearGradient id="g" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#fffaf2"/>
<stop offset="1" stop-color="#ead9bd"/>
</linearGradient>
</defs>
<rect class="icon-bg" x="6" y="6" width="88" height="88" rx="20"/>
<g class="icon-shape" color="#ad1d1c">
<path d="M32 18v23M68 18v23"/>
<path d="M26 41h48v13a24 24 0 0 1-48 0z"/>
<path d="M50 78v10M36 88h28"/>
<path d="M38 54h24"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 696 B

18
static/icon-dirs.svg Normal file
View File

@@ -0,0 +1,18 @@
<svg xmlns="http://www.w3.org/2000/svg" width="96" height="96" viewBox="0 0 100 100" role="img" aria-hidden="true">
<style>
.icon-bg{fill:url(#g);stroke:#d6bf9c;stroke-width:1.8}
.icon-shape{fill:none;stroke:#ad1d1c;stroke-width:5.2;stroke-linecap:round;stroke-linejoin:round}
</style>
<defs>
<linearGradient id="g" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#fffaf2"/>
<stop offset="1" stop-color="#ead9bd"/>
</linearGradient>
</defs>
<rect class="icon-bg" x="6" y="6" width="88" height="88" rx="20"/>
<g class="icon-shape" color="#ad1d1c">
<path d="M18 34h23l6 8h35v32a6 6 0 0 1-6 6H24a6 6 0 0 1-6-6z"/>
<path d="M18 34v-7a5 5 0 0 1 5-5h19l7 8h28a5 5 0 0 1 5 5v7"/>
<path d="M27 57h46"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 717 B

18
static/icon-general.svg Normal file
View File

@@ -0,0 +1,18 @@
<svg xmlns="http://www.w3.org/2000/svg" width="96" height="96" viewBox="0 0 100 100" role="img" aria-hidden="true">
<style>
.icon-bg{fill:url(#g);stroke:#d6bf9c;stroke-width:1.8}
.icon-shape{fill:none;stroke:#ad1d1c;stroke-width:5.2;stroke-linecap:round;stroke-linejoin:round}
</style>
<defs>
<linearGradient id="g" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#fffaf2"/>
<stop offset="1" stop-color="#ead9bd"/>
</linearGradient>
</defs>
<rect class="icon-bg" x="6" y="6" width="88" height="88" rx="20"/>
<g class="icon-shape" color="#ad1d1c">
<path d="M50 20v10M50 70v10M20 50h10M70 50h10M29 29l7 7M64 64l7 7M71 29l-7 7M36 64l-7 7"/>
<circle cx="50" cy="50" r="14"/>
<circle cx="50" cy="50" r="5" fill="currentColor" stroke="none"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 759 B

18
static/icon-groups.svg Normal file
View File

@@ -0,0 +1,18 @@
<svg xmlns="http://www.w3.org/2000/svg" width="96" height="96" viewBox="0 0 100 100" role="img" aria-hidden="true">
<style>
.icon-bg{fill:url(#g);stroke:#d6bf9c;stroke-width:1.8}
.icon-shape{fill:none;stroke:#ad1d1c;stroke-width:5.2;stroke-linecap:round;stroke-linejoin:round}
</style>
<defs>
<linearGradient id="g" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#fffaf2"/>
<stop offset="1" stop-color="#ead9bd"/>
</linearGradient>
</defs>
<rect class="icon-bg" x="6" y="6" width="88" height="88" rx="20"/>
<g class="icon-shape" color="#ad1d1c">
<path d="M24 29h27l8 8h17v34a6 6 0 0 1-6 6H24z"/>
<path d="M31 50h38M31 61h28"/>
<circle cx="72" cy="27" r="9"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 682 B

19
static/icon-logging.svg Normal file
View File

@@ -0,0 +1,19 @@
<svg xmlns="http://www.w3.org/2000/svg" width="96" height="96" viewBox="0 0 100 100" role="img" aria-hidden="true">
<style>
.icon-bg{fill:url(#g);stroke:#d6bf9c;stroke-width:1.8}
.icon-shape{fill:none;stroke:#ad1d1c;stroke-width:5.2;stroke-linecap:round;stroke-linejoin:round}
</style>
<defs>
<linearGradient id="g" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#fffaf2"/>
<stop offset="1" stop-color="#ead9bd"/>
</linearGradient>
</defs>
<rect class="icon-bg" x="6" y="6" width="88" height="88" rx="20"/>
<g class="icon-shape" color="#ad1d1c">
<path d="M28 16h34l10 10v58H28z"/>
<path d="M62 16v14h14"/>
<path d="M37 43h26M37 55h26M37 67h18"/>
<path d="M70 58l9 9-17 17-10 2 2-10z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 709 B

20
static/icon-queues.svg Normal file
View File

@@ -0,0 +1,20 @@
<svg xmlns="http://www.w3.org/2000/svg" width="96" height="96" viewBox="0 0 100 100" role="img" aria-hidden="true">
<style>
.icon-bg{fill:url(#g);stroke:#d6bf9c;stroke-width:1.8}
.icon-shape{fill:none;stroke:#ad1d1c;stroke-width:5.2;stroke-linecap:round;stroke-linejoin:round}
</style>
<defs>
<linearGradient id="g" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#fffaf2"/>
<stop offset="1" stop-color="#ead9bd"/>
</linearGradient>
</defs>
<rect class="icon-bg" x="6" y="6" width="88" height="88" rx="20"/>
<g class="icon-shape" color="#ad1d1c">
<path d="M30 20h40v20H30z"/>
<path d="M22 40h56a8 8 0 0 1 8 8v22H14V48a8 8 0 0 1 8-8z"/>
<path d="M30 62h40v22H30z"/>
<path d="M38 70h24"/>
<circle cx="72" cy="51" r="3" fill="currentColor" stroke="none"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 775 B

17
static/icon-security.svg Normal file
View File

@@ -0,0 +1,17 @@
<svg xmlns="http://www.w3.org/2000/svg" width="96" height="96" viewBox="0 0 100 100" role="img" aria-hidden="true">
<style>
.icon-bg{fill:url(#g);stroke:#d6bf9c;stroke-width:1.8}
.icon-shape{fill:none;stroke:#ad1d1c;stroke-width:5.2;stroke-linecap:round;stroke-linejoin:round}
</style>
<defs>
<linearGradient id="g" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#fffaf2"/>
<stop offset="1" stop-color="#ead9bd"/>
</linearGradient>
</defs>
<rect class="icon-bg" x="6" y="6" width="88" height="88" rx="20"/>
<g class="icon-shape" color="#ad1d1c">
<path d="M50 14l30 12v20c0 20-12 33-30 40-18-7-30-20-30-40V26z"/>
<path d="M37 51l9 9 18-23"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 664 B

23
static/icon-service.svg Normal file
View File

@@ -0,0 +1,23 @@
<svg xmlns="http://www.w3.org/2000/svg" width="96" height="96" viewBox="0 0 100 100" role="img" aria-hidden="true">
<style>
.icon-bg{fill:url(#g);stroke:#d6bf9c;stroke-width:1.8}
.icon-shape{fill:none;stroke:#ad1d1c;stroke-width:5.2;stroke-linecap:round;stroke-linejoin:round}
.icon-fill{fill:#ad1d1c;stroke:none}
</style>
<defs>
<linearGradient id="g" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#fffaf2"/>
<stop offset="1" stop-color="#ead9bd"/>
</linearGradient>
</defs>
<rect class="icon-bg" x="6" y="6" width="88" height="88" rx="20"/>
<g class="icon-shape">
<path d="M30 24h40a6 6 0 0 1 6 6v12H24V30a6 6 0 0 1 6-6z"/>
<path d="M24 42h52v28a6 6 0 0 1-6 6H30a6 6 0 0 1-6-6z"/>
<path d="M34 34h2M45 34h2M56 34h2"/>
<path d="M37 58h26"/>
<path d="M50 49v18"/>
<path d="M68 54l7 7-7 7"/>
<path d="M32 54l-7 7 7 7"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 843 B

18
static/icon-smart.svg Normal file
View File

@@ -0,0 +1,18 @@
<svg xmlns="http://www.w3.org/2000/svg" width="96" height="96" viewBox="0 0 100 100" role="img" aria-hidden="true">
<style>
.icon-bg{fill:url(#g);stroke:#d6bf9c;stroke-width:1.8}
.icon-shape{fill:none;stroke:#ad1d1c;stroke-width:5.2;stroke-linecap:round;stroke-linejoin:round}
</style>
<defs>
<linearGradient id="g" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#fffaf2"/>
<stop offset="1" stop-color="#ead9bd"/>
</linearGradient>
</defs>
<rect class="icon-bg" x="6" y="6" width="88" height="88" rx="20"/>
<g class="icon-shape" color="#ad1d1c">
<path d="M34 63c-12-10-11-29 4-39 13-9 34-2 37 15 3 19-16 29-25 40"/>
<path d="M36 63h28M39 74h22"/>
<path d="M41 39c6-7 15-7 21 0M43 50c5 4 10 4 15 0"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 723 B

22
static/icon-start.svg Normal file
View File

@@ -0,0 +1,22 @@
<svg xmlns="http://www.w3.org/2000/svg" width="96" height="96" viewBox="0 0 100 100" role="img" aria-hidden="true">
<style>
.icon-bg{fill:url(#g);stroke:#d6bf9c;stroke-width:1.8}
.icon-shape{fill:none;stroke:#ad1d1c;stroke-width:5.2;stroke-linecap:round;stroke-linejoin:round}
.icon-fill{fill:#ad1d1c;stroke:none}
</style>
<defs>
<linearGradient id="g" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#fffaf2"/>
<stop offset="1" stop-color="#ead9bd"/>
</linearGradient>
</defs>
<rect class="icon-bg" x="6" y="6" width="88" height="88" rx="20"/>
<g class="icon-shape">
<path d="M24 62c0-20 14-38 34-43"/>
<path d="M58 19l-6 13 15-4"/>
<path d="M28 70h44"/>
<path d="M35 80h30"/>
<path d="M50 38v18"/>
<path d="M41 47l9 9 13-17"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 751 B

19
static/icon-stations.svg Normal file
View File

@@ -0,0 +1,19 @@
<svg xmlns="http://www.w3.org/2000/svg" width="96" height="96" viewBox="0 0 100 100" role="img" aria-hidden="true">
<style>
.icon-bg{fill:url(#g);stroke:#d6bf9c;stroke-width:1.8}
.icon-shape{fill:none;stroke:#ad1d1c;stroke-width:5.2;stroke-linecap:round;stroke-linejoin:round}
</style>
<defs>
<linearGradient id="g" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#fffaf2"/>
<stop offset="1" stop-color="#ead9bd"/>
</linearGradient>
</defs>
<rect class="icon-bg" x="6" y="6" width="88" height="88" rx="20"/>
<g class="icon-shape" color="#ad1d1c">
<circle cx="50" cy="50" r="8"/>
<path d="M35 35a22 22 0 0 0 0 30M65 35a22 22 0 0 1 0 30"/>
<path d="M25 25a36 36 0 0 0 0 50M75 25a36 36 0 0 1 0 50"/>
<path d="M50 58v22"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 741 B

18
static/icon-susers.svg Normal file
View File

@@ -0,0 +1,18 @@
<svg xmlns="http://www.w3.org/2000/svg" width="96" height="96" viewBox="0 0 100 100" role="img" aria-hidden="true">
<style>
.icon-bg{fill:url(#g);stroke:#d6bf9c;stroke-width:1.8}
.icon-shape{fill:none;stroke:#ad1d1c;stroke-width:5.2;stroke-linecap:round;stroke-linejoin:round}
</style>
<defs>
<linearGradient id="g" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#fffaf2"/>
<stop offset="1" stop-color="#ead9bd"/>
</linearGradient>
</defs>
<rect class="icon-bg" x="6" y="6" width="88" height="88" rx="20"/>
<g class="icon-shape" color="#ad1d1c">
<circle cx="50" cy="32" r="14"/>
<path d="M25 78c3-18 14-28 25-28s22 10 25 28z"/>
<path d="M34 78h32"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 673 B

19
static/icon-users.svg Normal file
View File

@@ -0,0 +1,19 @@
<svg xmlns="http://www.w3.org/2000/svg" width="96" height="96" viewBox="0 0 100 100" role="img" aria-hidden="true">
<style>
.icon-bg{fill:url(#g);stroke:#d6bf9c;stroke-width:1.8}
.icon-shape{fill:none;stroke:#ad1d1c;stroke-width:5.2;stroke-linecap:round;stroke-linejoin:round}
</style>
<defs>
<linearGradient id="g" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#fffaf2"/>
<stop offset="1" stop-color="#ead9bd"/>
</linearGradient>
</defs>
<rect class="icon-bg" x="6" y="6" width="88" height="88" rx="20"/>
<g class="icon-shape" color="#ad1d1c">
<circle cx="38" cy="34" r="12"/>
<circle cx="65" cy="36" r="10"/>
<path d="M16 78c3-17 12-26 23-26s20 9 23 26z"/>
<path d="M52 78c2-12 9-20 18-20 7 0 13 5 16 20z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 734 B

19
static/icon-volumes.svg Normal file
View File

@@ -0,0 +1,19 @@
<svg xmlns="http://www.w3.org/2000/svg" width="96" height="96" viewBox="0 0 100 100" role="img" aria-hidden="true">
<style>
.icon-bg{fill:url(#g);stroke:#d6bf9c;stroke-width:1.8}
.icon-shape{fill:none;stroke:#ad1d1c;stroke-width:5.2;stroke-linecap:round;stroke-linejoin:round}
</style>
<defs>
<linearGradient id="g" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#fffaf2"/>
<stop offset="1" stop-color="#ead9bd"/>
</linearGradient>
</defs>
<rect class="icon-bg" x="6" y="6" width="88" height="88" rx="20"/>
<g class="icon-shape" color="#ad1d1c">
<path d="M25 18h50v64H25z"/>
<path d="M34 18v24h32V18"/>
<path d="M36 64h28"/>
<circle cx="66" cy="70" r="4" fill="currentColor" stroke="none"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 714 B

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

@@ -1,44 +0,0 @@
<HTML>
<BODY BGCOLOR=#F0F0FF>
<TABLE BORDER=0 CELLSPACING=0 WIDTH=100%>
<TR BGCOLOR=#C0C0FF>
<TD>
<B><FONT SIZE=+2>SMArT v0.99 (Perl)</FONT></B>
</TD>
<TD ALIGN=RIGHT>
<IMG SRC="/static/smart_icon.jpg"><BR>
</TD>
</TR>
<TR BGCOLOR=#D0D0FF>
<TD COLSPAN=2>
Welcome to SMArT, the easy way to configure mars_nwe, <B>Mar</B>tin <B>S</B>tover's <B>N</B>et<B>W</B>are <B>e</B>mulator.
</TD>
</TR>
<TR BGCOLOR=#E0E0FF>
<TD COLSPAN=2>
To apply the changes you have made to the configuration, you have to restart <TT>MARS_NWE</TT>
</TD>
</TR>
<TR BGCOLOR=#E0E0FF>
<TH ALIGN=CENTER>
<A HREF="/cgi-bin/control?start">Start <TT>MARS_NWE</TT>
</TH>
<TH ALIGN=CENTER>
<A HREF="/cgi-bin/control?stop">Stop <TT>MARS_NWE</TT>
</TH>
</TR>
<TR BGCOLOR=#E0E0FF>
<TD COLSPAN=2 ALIGN=CENTER>
<BR>The newest version of SMArT can be downloaded from <A HREF="http://www.lintux.cx/" TARGET="_parent">my website</A>.
</TD>
</TR>
<TR BGCOLOR=#E0E0FF>
<TH COLSPAN=2>
<BR>&copy; Copyright 2001 <A HREF="mailto:lintux@lintux.cx">Wilmer van der Gaast</A>
</TH>
</TR>
</TABLE>
</BODY>
</HTML>

119
static/start.html.cmake Normal file
View File

@@ -0,0 +1,119 @@
<!DOCTYPE html>
<HTML>
<HEAD>
<TITLE>SMArT</TITLE>
<META CHARSET="utf-8">
<META NAME="viewport" CONTENT="width=device-width, initial-scale=1">
<LINK REL="icon" HREF="/static/favicon.ico" TYPE="image/x-icon">
<LINK REL="icon" TYPE="image/png" SIZES="32x32" HREF="/static/favicon-32x32.png">
<LINK REL="icon" TYPE="image/png" SIZES="16x16" HREF="/static/favicon-16x16.png">
<LINK REL="apple-touch-icon" HREF="/static/apple-touch-icon.png">
<STYLE TYPE="text/css">
BODY {
margin: 0;
padding: 22px;
background: #f6f1ea;
color: #342a25;
font-family: Arial, Helvetica, sans-serif;
}
A { color: #9f1f1f; text-decoration: none; }
A:hover { text-decoration: underline; }
.wrapper {
max-width: 900px;
margin: 0 auto;
background: #fffdfa;
border: 1px solid #e5d6c6;
border-radius: 16px;
overflow: hidden;
box-shadow: 0 10px 30px rgba(60, 30, 10, 0.10);
}
.hero {
padding: 22px 24px;
background: linear-gradient(135deg, #7d1018, #b82929 60%, #d7b169);
color: #ffffff;
}
.hero TABLE { width: 100%; border-collapse: collapse; }
.hero-title {
font-size: 30px;
font-weight: bold;
}
.hero-sub {
margin-top: 6px;
color: #f8e6c8;
font-size: 14px;
}
.hero IMG { max-height: 56px; }
.body {
padding: 22px;
}
.notice {
padding: 14px 16px;
margin-bottom: 14px;
border: 1px solid #ecdcc8;
border-radius: 12px;
background: #fbf6ef;
}
.actions TABLE { width: 100%; border-collapse: separate; border-spacing: 12px; }
.action {
display: block;
padding: 16px 18px;
border-radius: 12px;
text-align: center;
font-size: 18px;
font-weight: bold;
color: #ffffff;
text-decoration: none;
background: #9f1f1f;
}
.action.secondary {
background: #6f5b4f;
}
.footer {
padding-top: 8px;
font-size: 13px;
color: #6c5b52;
}
</STYLE>
</HEAD>
<BODY>
<DIV CLASS="wrapper">
<DIV CLASS="hero">
<TABLE BORDER="0" CELLSPACING="0" CELLPADDING="0">
<TR>
<TD>
<DIV CLASS="hero-title">SMArT v@MARS_NWE_VERSION@</DIV>
<DIV CLASS="hero-sub">Perl interface for configuring MARS_NWE</DIV>
</TD>
<TD ALIGN="RIGHT">
<IMG SRC="/static/smart_icon.jpg" ALT="SMArT">
</TD>
</TR>
</TABLE>
</DIV>
<DIV CLASS="body">
<DIV CLASS="notice">
Welcome to SMArT, the easy way to configure <B>MARS_NWE</B>.
</DIV>
<DIV CLASS="notice">
To apply changes to the configuration, restart <TT>MARS_NWE</TT> after saving your settings.
</DIV>
<DIV CLASS="actions">
<TABLE BORDER="0" CELLSPACING="0" CELLPADDING="0">
<TR>
<TD WIDTH="50%"><A CLASS="action" HREF="/service/control?start">Start <TT>MARS_NWE</TT></A></TD>
<TD WIDTH="50%"><A CLASS="action secondary" HREF="/service/control?stop">Stop <TT>MARS_NWE</TT></A></TD>
</TR>
<TR>
<TD WIDTH="50%"><A CLASS="action secondary" HREF="/service/control?restart">Restart <TT>MARS_NWE</TT></A></TD>
<TD WIDTH="50%"><A CLASS="action secondary" HREF="/service/control?status">Status <TT>MARS_NWE</TT></A></TD>
</TR>
</TABLE>
</DIV>
<DIV CLASS="footer">
The newest version of SMArT can be downloaded from <A HREF="http://www.lintux.cx/" TARGET="_parent">the project website</A>.<BR><BR>
&copy; Copyright 2026 <A HREF="mailto:mario.fetka@disconnected-by-peer.at">Mario Fetka</A>
</DIV>
</DIV>
</DIV>
</BODY>
</HTML>