Compare commits

..

24 Commits

Author SHA1 Message Date
Mario Fetka
9b49ae2903 add print test page v6 2026-05-22 15:16:45 +02:00
Mario Fetka
111f3cae77 add print test page v5 2026-05-22 15:08:18 +02:00
Mario Fetka
672a8c9fd9 add print test page v4 2026-05-22 15:02:58 +02:00
Mario Fetka
04275ca1a7 add print test page v3 2026-05-22 14:57:13 +02:00
Mario Fetka
1f2c12fc33 add print test page v2 2026-05-22 14:45:54 +02:00
Mario Fetka
5bb5ef98c5 add print test page 2026-05-22 14:37:21 +02:00
Mario Fetka
d967947ece Release Polish Step 2 2026-05-22 11:07:00 +02:00
Mario Fetka
8bc318baf2 Release Polish Step 1 / README + smart.conf 2026-05-22 11:02:43 +02:00
Mario Fetka
46a3d9f653 Native Helper Logging 2026-05-22 10:42:58 +02:00
Mario Fetka
56b70a6ffc nwwebui named log levels. 2026-05-22 10:28:17 +02:00
Mario Fetka
e8f1050246 add smart_userlist 2026-05-22 10:15:16 +02:00
Mario Fetka
a8560e6de5 Remove unreferenced screenshot 2026-05-22 10:08:26 +02:00
Mario Fetka
cc94aa326d add readme screnshots anon 2026-05-22 10:04:04 +02:00
Mario Fetka
ee40a44e9f add readme fix up 2026-05-22 09:59:21 +02:00
Mario Fetka
aad1f8062e add readme fix up 2026-05-22 09:58:38 +02:00
Mario Fetka
668982ea22 add readme 2026-05-22 09:40:14 +02:00
Mario Fetka
02b7984e17 add readme 2026-05-22 09:40:02 +02:00
Mario Fetka
2529e0e9dd Big collection 2 2026-05-22 09:07:47 +02:00
Mario Fetka
58a2e8da04 Big collection 2026-05-22 07:32:17 +02:00
Mario Fetka
6998f5990d Default to services 2026-05-21 23:31:22 +02:00
Mario Fetka
7549a2021e Login Logo big and copyright Fix 2026-05-21 23:27:13 +02:00
Mario Fetka
b5a8e88068 Login Logo big and copyright 2026-05-21 23:19:32 +02:00
Mario Fetka
ce43a20174 Login Logo Fix 2026-05-21 23:10:09 +02:00
Mario Fetka
ba8211e0da Logging stderr 2026-05-21 23:03:32 +02:00
35 changed files with 3217 additions and 276 deletions

452
README.md
View File

@@ -2,7 +2,46 @@
SMArT is the web-based configuration interface for **MARS_NWE**, a Novell NetWare 3.x emulator for Linux and FreeBSD. 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. In the current setup, this repository is integrated into the main `mars_nwe` project as a **Git submodule**. It is built, installed, and released as part of the normal **MARS_NWE** release process rather than as a separate end-user component.
## Screenshots
The main menu is the normal entry point after login. It shows the available
configuration sections on the left and opens the selected explanation or editor
view on the right.
All screenshots in this section use anonymized example values for users,
hostnames, printer names and local mountpoints.
![SMArT main menu](doc/screenshots/main-menu.png)
Additional UI examples:
<table>
<tr>
<td width="50%"><a href="doc/screenshots/login.png"><img src="doc/screenshots/thumbs/login.png" alt="Login screen"></a><br><strong>Login</strong><br>PAM-based login with SMArT session cookies.</td>
<td width="50%"><a href="doc/screenshots/service-runtime.png"><img src="doc/screenshots/thumbs/service-runtime.png" alt="Runtime information"></a><br><strong>Runtime information</strong><br>Configured paths, service name and project link.</td>
</tr>
<tr>
<td width="50%"><a href="doc/screenshots/volumes-import.png"><img src="doc/screenshots/thumbs/volumes-import.png" alt="Volume import"></a><br><strong>Volume import</strong><br>Detected host mountpoints can be imported as MARS_NWE volumes.</td>
<td width="50%"><a href="doc/screenshots/user-editor.png"><img src="doc/screenshots/thumbs/user-editor.png" alt="User editor"></a><br><strong>User editor</strong><br>Optional Unix user mapping and bindery group membership.</td>
</tr>
<tr>
<td width="50%"><a href="doc/screenshots/print-queue-cups.png"><img src="doc/screenshots/thumbs/print-queue-cups.png" alt="Print queue CUPS integration"></a><br><strong>Print queues</strong><br>CUPS printer selection can prefill the Unix print command.</td>
<td width="50%"><a href="doc/screenshots/smart-settings.png"><img src="doc/screenshots/thumbs/smart-settings.png" alt="SMArT settings"></a><br><strong>SMArT settings</strong><br>Bindery server settings and server-name synchronization.</td>
</tr>
<tr>
<td width="50%"><a href="doc/screenshots/general-settings.png"><img src="doc/screenshots/thumbs/general-settings.png" alt="General settings"></a><br><strong>General settings</strong><br>Core MARS_NWE options with synchronized bindery naming.</td>
<td width="50%"><a href="doc/screenshots/advanced-path-settings.png"><img src="doc/screenshots/thumbs/advanced-path-settings.png" alt="Advanced path settings"></a><br><strong>Advanced sections</strong><br>Only the matching advanced section is displayed for the selected menu item.</td>
</tr>
<tr>
<td width="50%"><a href="doc/screenshots/validation-error.png"><img src="doc/screenshots/thumbs/validation-error.png" alt="Validation error"></a><br><strong>Validation errors</strong><br>Invalid input is rejected with a focused error page and a back action.</td>
<td width="50%"><a href="doc/screenshots/service-control.png"><img src="doc/screenshots/thumbs/service-control.png" alt="Service control output"></a><br><strong>Service control</strong><br>Start, stop, restart and status output for the configured MARS_NWE service.</td>
</tr>
</table>
A compact maintainer overview of the screenshot set is available as
[`doc/screenshots/screenshot-contact-sheet.png`](doc/screenshots/screenshot-contact-sheet.png).
## Project status and integration ## Project status and integration
@@ -10,40 +49,216 @@ This repository is intended to be embedded into the main `mars_nwe` Git reposito
- Main project: `mars_nwe` - Main project: `mars_nwe`
- Submodule role: provides the SMArT web UI and helper tools - 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 - Release model: shipped as part of the integrated **MARS_NWE** release
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. The build and install rules install the web UI binary, Perl helpers, configuration template, static assets, systemd unit, and PAM file as part of the integrated installation target.
## Architecture overview ## Architecture overview
SMArT consists of two main parts: SMArT consists of three main pieces:
1. **Perl-based application logic** for configuration pages and helper scripts 1. **`nwwebui`** a dedicated HTTP/HTTPS frontend service
2. **`nwwebui` service** as the web frontend that exposes the application over HTTP and HTTPS 2. **Perl helper scripts** request routing, configuration pages, bindery operations, validation, and service control
3. **Small native helpers** PAM login checking and optional user enumeration helpers
The current implementation adds a dedicated `nwwebui` service that can serve the application directly over: The `nwwebui` service can expose the UI over:
- **HTTP on port 9080** - **HTTP on port 9080**
- **HTTPS on port 9443** - **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. Both listeners can run in parallel. HTTPS is recommended for real deployments because the login form transmits credentials. Plain HTTP is still useful for local testing or trusted internal environments.
## Security model ## Major features
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: ### HTML login, sessions, and logout
- `/etc/pam.d/smart` SMArT provides a form-based login page, session cookies, and a logout action. Static assets such as the SMArT logo are served before authentication so the login page can render correctly.
That means no manual PAM file deployment is normally required when SMArT is installed through the integrated `mars_nwe` package or release. Runtime session files are stored under the WebUI runtime directory, typically:
```text
/run/mars-nwe-webui
```
### PAM authentication with administrator group restriction
Authentication is performed through the PAM service `smart` using the `check_login` helper. In addition to a successful PAM login, the user must be a member of the configured Unix administrator group.
The administrator group is configured at build time through the main `mars_nwe` CMake project:
```bash
cmake -DMARS_NWE_SMART_ADMIN_GROUP=root ...
```
The default is `root` to preserve the traditional behavior on existing systems. On normal Unix systems the `root` user has primary group `root`, so root can still log in. For delegated administration, build with a dedicated group instead:
```bash
cmake -DMARS_NWE_SMART_ADMIN_GROUP=nwadmin ...
groupadd nwadmin
usermod -aG nwadmin mario
```
After installation the effective setting appears in `smart.conf` as:
```perl
$smart_admin_group = 'root';
```
or, for a delegated build:
```perl
$smart_admin_group = 'nwadmin';
```
### Service control page
The start page includes controls for the configured MARS_NWE service:
- start
- stop
- restart
- status
The service name is supplied by the build configuration and can be overridden in `smart.conf`:
```perl
$mars_nwe_service = 'mars-nwe-serv.service';
$smart_systemctl_path = '/usr/bin/systemctl';
```
### Runtime information page
The start page shows the important runtime paths generated by CMake, including:
- main MARS_NWE configuration file
- SMArT configuration file
- WebUI helper/script directory
- MARS_NWE service unit name
- `systemctl` executable
This helps diagnose packaging or installation path issues without searching through generated files.
### Configuration editors and advanced sections
The main menu contains the commonly used configuration areas and advanced sections. Advanced pages are shown only when the selected section is opened, which keeps the menu usable while still exposing low-level MARS_NWE options.
Current sections include, among others:
- setup first
- MARS_NWE service
- general settings
- directories
- precompiled/path settings
- security
- user configuration
- volumes
- devices
- logging
- stations/access control
- users
- groups
- print queues
### Import helpers
SMArT can prefill or discover host-side data for common configuration tasks:
- local mount points for volume creation
- Unix users for MARS_NWE user mapping
- CUPS printers for print queue command generation
- IPX interfaces for device configuration
These helpers are meant to reduce manual typing while still leaving the final configuration under administrator control.
### Validation and error pages
The apply path validates common input before writing configuration or changing bindery data. Invalid values are shown on a dedicated validation page instead of failing silently or returning an empty HTTP response.
Validation currently covers areas such as:
- volume names and Unix paths
- device/network parameters
- print queue names, print commands, and spool directories
- user names and group names
- invalid bindery characters
### Bindery command handling
Bindery operations are executed through checked helper functions instead of silent `system()` calls. The WebUI logs command start, command success, command failure, and relevant output.
Commands such as `nwbocreate`, `nwbprm`, and `nwborm` are handled through `run_bindery_cmd()`. Pipe-style `nwbpset` operations are handled through `run_bindery_pipe()` using a temporary input file and checked return code.
This improves diagnostics for user, group, and print queue operations. The browser receives a structured error page when a bindery command fails.
### Bindery success pages
After successful user, group, or print queue changes, SMArT can show a result page with the number of successful bindery commands. This makes bindery changes visible to the administrator and avoids silent redirects after complex operations.
### Optional Unix user mapping updates
Existing MARS_NWE users no longer have their `UNIX_USER` mapping removed unless the administrator explicitly requests a mapping change. This prevents accidental loss of Unix user assignments when only editing full name, password, or group membership.
## Logging
SMArT has two relevant log streams:
- the `nwwebui` service log
- the Perl frontend log, normally `smart.log`
Typical paths:
```text
/var/log/mars_nwe/nwwebui.log
/var/log/mars_nwe/smart.log
```
### Perl frontend log level
The Perl frontend log level is configured in `smart.conf`:
```perl
# SMArT Perl logging verbosity.
# Values: error, warning, info, debug, trace
# Default: info
$smart_debug_level = 'info';
```
Supported values, from quiet to verbose:
- `error` only real errors that abort or fail an operation
- `warning` errors and warnings about unusual but non-fatal situations
- `info` normal operational messages, command start/finish, default
- `debug` additional diagnostic information for troubleshooting
- `trace` very verbose step-by-step traces, including bindery pipe payloads
Use `trace` only while debugging a concrete problem. It may include submitted bindery payload data and can produce a lot of log output. After debugging, switch back to `info`.
### `nwwebui` service log level
The `nwwebui` service has its own numeric log level:
```perl
$nw_log_level = 'info';
```
Typical meanings:
- `0` = errors only
- `1` = informational messages
- `2` = debug output
## Installed components ## Installed components
The install rules include the following relevant components.
### Binaries ### Binaries
- `nwwebui` dedicated web service frontend - `nwwebui` dedicated web service frontend
- `check_login` PAM authentication helper - `check_login` PAM authentication and administrator-group helper
- optional host discovery helpers, depending on build options
### Native helper tools
- `check_login` validates PAM credentials and verifies membership in the configured SMArT administrator group
- `smart_userlist` lists local Unix users for the optional bindery-to-Unix user mapping selector
### Perl helpers ### Perl helpers
@@ -52,77 +267,67 @@ The install rules include the following relevant components.
- `readconfig.pl` - `readconfig.pl`
- `settings.pl` - `settings.pl`
- `static.pl` - `static.pl`
- `control`
### Configuration and assets ### Configuration and assets
- `smart.conf` - `smart.conf`
- static HTML and image assets for the web UI - static HTML/image assets for the WebUI
- optional `mars-nwe-webui.service` systemd unit - optional `mars-nwe-webui.service` systemd unit
- PAM file installed as `/etc/pam.d/smart` - PAM file installed as `/etc/pam.d/smart`
- local Unix user-list helper used by the user editor
These components are all installed by the build system as part of the same integrated installation target.
## Typical runtime paths ## Typical runtime paths
The original templates use CMake placeholders. For documentation, the following standard example paths can be used in a typical Linux installation: The templates use CMake placeholders. In a typical Linux installation, the effective paths are similar to:
- Main MARS_NWE config directory: `/etc/mars_nwe` - Main MARS_NWE config directory: `/etc/mars_nwe`
- SMArT config file: `/etc/mars_nwe/smart.conf` - SMArT config file: `/etc/mars_nwe/smart.conf`
- Main MARS_NWE server config: `/etc/mars_nwe/nwserv.conf` - Main MARS_NWE server config: `/etc/mars_nwe/nwserv.conf`
- Helper binaries and scripts: `/usr/libexec/mars_nwe` - Helper binaries and scripts, including `smart`, `check_login`, `smart_userlist` and Perl helpers: `/usr/libexec/mars_nwe`
- Static SMArT assets: `/usr/libexec/mars_nwe/static` - Static SMArT assets: `/usr/libexec/mars_nwe/static`
- Log directory: `/var/log/mars_nwe` - Log directory: `/var/log/mars_nwe`
- Runtime/session directory: `/run/mars-nwe-webui`
- PID directory: `/run/mars_nwe` - PID directory: `/run/mars_nwe`
- TLS certificate: `/etc/mars_nwe/server.crt` - TLS certificate: `/etc/mars_nwe/server.crt`
- TLS private key: `/etc/mars_nwe/server.key` - TLS private key: `/etc/mars_nwe/server.key`
- PAM file: `/etc/pam.d/smart` - PAM file: `/etc/pam.d/smart`
These values are sensible standard defaults for documentation. Packaging may still adjust them depending on the target distribution. Packaging may adjust these paths depending on the target distribution.
## The `smart.conf` file ## `smart.conf` example
The `smart.conf` file controls both the SMArT frontend behavior and the `nwwebui` listener settings. A documented example with standard installation paths:
A documented example with standard installation paths is shown below:
```perl ```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 # Main MARS_NWE configuration
# ------------------------------------------------------------
$mars_config = '/etc/mars_nwe/nwserv.conf'; $mars_config = '/etc/mars_nwe/nwserv.conf';
$nonroot_user = 'nobody'; $nonroot_user = 'nobody';
$smart_compact_nwservconf = 0; $smart_compact_nwservconf = 0;
# ------------------------------------------------------------
# SMArT internal file layout # SMArT internal file layout
# ------------------------------------------------------------
$smart_conf_path = '/etc/mars_nwe/smart.conf'; $smart_conf_path = '/etc/mars_nwe/smart.conf';
$smart_nwclient_path = '/etc/mars_nwe/.nwclient'; $smart_nwclient_path = '/etc/mars_nwe/.nwclient';
$smart_static_dir = '/usr/libexec/mars_nwe/static'; $smart_static_dir = '/usr/libexec/mars_nwe/static';
$smart_log_path = '/var/log/mars_nwe/smart.log'; $smart_log_path = '/var/log/mars_nwe/smart.log';
$smart_check_login = '/usr/libexec/mars_nwe/check_login'; $smart_check_login = '/usr/libexec/mars_nwe/check_login';
$smart_admin_group = 'root';
# Optional override, usually not needed # Perl frontend logging
# $smart_perl_path = '/usr/libexec/mars_nwe/smart'; $smart_debug_level = 'info';
# Service control
$mars_nwe_service = 'mars-nwe-serv.service';
$smart_systemctl_path = '/usr/bin/systemctl';
# CUPS helper integration
$smart_cups_enable = '1';
$smart_cups_lpstat_path = '/usr/bin/lpstat';
$smart_cups_print_command_template = '/usr/bin/lp -d %p -';
# ------------------------------------------------------------
# nwwebui listener settings # nwwebui listener settings
# ------------------------------------------------------------
$nw_bind_ip = '0.0.0.0'; $nw_bind_ip = '0.0.0.0';
$nw_log_level = 1; $nw_log_level = 'info';
$nw_daemonize = 0; $nw_daemonize = 0;
$nw_pid_file = '/run/mars_nwe/nwwebui.pid'; $nw_pid_file = '/run/mars_nwe/nwwebui.pid';
$nw_log_file = '/var/log/mars_nwe/nwwebui.log'; $nw_log_file = '/var/log/mars_nwe/nwwebui.log';
@@ -135,68 +340,64 @@ $nw_cert_file = '/etc/mars_nwe/server.crt';
$nw_key_file = '/etc/mars_nwe/server.key'; $nw_key_file = '/etc/mars_nwe/server.key';
``` ```
## `smart.conf` settings explained ### Print queue test action
### UI colors The print queue page can submit a small test job to an existing MARS_NWE queue
through the ncpfs `nprint` tool. This verifies the queue through the NetWare
print-queue path instead of only checking the local Unix/CUPS command.
These variables define the default SMArT page colors: The helper path can be adjusted in `smart.conf`:
- `$COLOR_BACK` page background ```perl
- `$COLOR_HEAD_BACK` / `$COLOR_HEAD_FORE` main section header colors $smart_nprint_path = '/usr/bin/nprint';
- `$COLOR_SUBH_BACK` / `$COLOR_SUBH_FORE` subsection header colors ```
- `$COLOR_TEXT_BACK` / `$COLOR_TEXT_FORE` regular content row colors
### Main MARS_NWE configuration ## Recent SMArT WebUI features
- `$mars_config` path to the main `nwserv.conf` file that SMArT reads and updates The current WebUI includes several usability and safety improvements:
- `$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 - HTML login, session cookies and logout handling
- static asset serving before login, so the login page can load the SMArT logo
- service-control pages for start, stop, restart and status actions
- runtime information on the start page, including generated paths and service name
- import helpers for CUPS printers, IPX interfaces, Unix users and local mountpoints
- `smart_userlist` helper for reliable Unix-user enumeration without parsing page output
- input validation pages for volumes, devices, print queues, users and groups
- stricter bindery object-name validation
- optional Unix user mapping for bindery users
- delete confirmation pages
- bindery command and bindery pipe logging
- success and error pages for bindery operations
- a `UNIX_USER` guard so existing users are not remapped unless requested
- configurable Perl log verbosity through `$smart_debug_level`
- `$smart_conf_path` absolute path to `smart.conf` ## Build and installation notes
- `$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 This repository is built as part of the main `mars_nwe` build. The build system:
- `$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 - generates `smart.conf` from the template
- `$nw_log_level` service log verbosity: - generates the main `smart` Perl launcher script
- `0` = errors only - builds `nwwebui`
- `1` = informational messages - builds `check_login`
- `2` = debug output - builds `smart_userlist`
- `$nw_daemonize` whether `nwwebui` detaches into the background - installs the PAM file and static UI assets
- `$nw_pid_file` location of the PID file - installs helper scripts and optional systemd units
- `$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: Useful build-time settings include:
- `nwwebui` listens on `9080` for HTTP by default ```bash
- `9443` is used for HTTPS cmake -DMARS_NWE_SMART_ADMIN_GROUP=root ...
- the log file can be configured with `$nw_log_file` cmake -DMARS_NWE_SMART_ADMIN_GROUP=nwadmin ...
- the log path can also be overridden at runtime with `-l` cmake -DMARS_NWE_SYSTEMD_SERVICE=mars-nwe-serv.service ...
```
Because passwords may be transmitted during login, HTTPS is the recommended way to access the interface. The administrator group defaults to `root` for compatibility. Use a dedicated group such as `nwadmin` when non-root administrators should be allowed to access the WebUI.
## Starting the service ## 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 ### 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. A typical installed system uses the `mars-nwe-webui.service` unit:
Typical commands:
```bash ```bash
systemctl enable --now mars-nwe-webui.service systemctl enable --now mars-nwe-webui.service
@@ -206,7 +407,7 @@ systemctl restart mars-nwe-webui.service
systemctl status mars-nwe-webui.service systemctl status mars-nwe-webui.service
``` ```
The service unit starts `nwwebui` with the equivalent of: The service starts `nwwebui` with the equivalent of:
```bash ```bash
/usr/sbin/nwwebui -c /etc/mars_nwe/smart.conf /usr/sbin/nwwebui -c /etc/mars_nwe/smart.conf
@@ -214,8 +415,6 @@ The service unit starts `nwwebui` with the equivalent of:
### Starting from the command line ### Starting from the command line
`nwwebui` can also be launched manually. The built-in usage text documents the supported options:
```text ```text
Usage: nwwebui [-h] [-d] [-s] [-c <smart.conf>] [-p <pidfile>] [-l <logfile>] Usage: nwwebui [-h] [-d] [-s] [-c <smart.conf>] [-p <pidfile>] [-l <logfile>]
@@ -228,30 +427,15 @@ Options:
-l, --logfile <file> Override log file path -l, --logfile <file> Override log file path
``` ```
Typical examples: Examples:
```bash ```bash
# start in foreground with the standard configuration
/usr/sbin/nwwebui -c /etc/mars_nwe/smart.conf /usr/sbin/nwwebui -c /etc/mars_nwe/smart.conf
# start in daemon mode
/usr/sbin/nwwebui -d -c /etc/mars_nwe/smart.conf /usr/sbin/nwwebui -d -c /etc/mars_nwe/smart.conf
# stop a running instance
/usr/sbin/nwwebui -s -c /etc/mars_nwe/smart.conf /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 /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: Typical access URLs:
- `http://<host>:9080/` - `http://<host>:9080/`
@@ -259,18 +443,46 @@ Typical access URLs:
For production use, HTTPS should be preferred. For production use, HTTPS should be preferred.
## Build and installation notes ## Native helper logging
This repository is built as part of the main `mars_nwe` build. The build system: The native helper programs `check_login` and `smart_userlist` read their log
destination and verbosity from `smart.conf` when called by the WebUI.
- generates `smart.conf` from the template They use the same Perl frontend settings:
- 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. ```perl
$smart_log_path = '/var/log/mars_nwe/smart.log';
$smart_debug_level = 'info';
```
The generated `config.h` also provides fallback defaults for these values, so
the helpers can still write useful diagnostics when they are executed manually
or before `smart.conf` could be loaded.
`check_login` logs authentication and authorization results, but never logs the
submitted password. `smart_userlist` keeps its tab-separated user-list output
on stdout unchanged and writes diagnostics only to the configured log file.
## Unix user discovery helper
The WebUI user editor can assign a MARS_NWE bindery user to a local Unix user.
For this selector, SMArT uses the native `smart_userlist` helper.
`smart_userlist` enumerates local users through the system user database instead
of relying on fragile parsing in the web page itself. This keeps the optional
Unix-user mapping UI usable even when the available users come from NSS-backed
sources such as local files, LDAP, SSSD or similar site-specific setups.
The helper is installed together with the other SMArT native tools and is
normally referenced from `smart.conf` as:
```perl
$smart_userlist_path = '/usr/libexec/mars_nwe/smart_userlist';
```
If the path is not set explicitly, SMArT falls back to the standard libexec
location generated by the build system.
## Summary ## 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. SMArT is now an integrated part of the `mars_nwe` release. It includes a dedicated `nwwebui` service, form-based sessions, PAM authentication with configurable administrator group restriction, service control, validation pages, import helpers, improved bindery command handling, and configurable logging.

1364
apply.pl

File diff suppressed because it is too large Load Diff

View File

@@ -1,32 +1,266 @@
/* /*
SMArT SMArT
Check username/password combination using PAM Check username/password combination using PAM and require membership in
the configured SMArT administrator Unix group.
Copyright 2001 Wilmer van der Gaast Usage:
check_login <user> <password> <admin-group> [smart.conf]
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
Passwords are never written to the log.
*/ */
#include <ctype.h>
#include <errno.h>
#include <grp.h>
#include <pwd.h>
#include <security/pam_appl.h> #include <security/pam_appl.h>
#include <stdarg.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#include "config.h"
int my_conv(int num_msg, const struct pam_message **msg, struct pam_response **resp, void *appdata_ptr); int my_conv(int num_msg, const struct pam_message **msg, struct pam_response **resp, void *appdata_ptr);
static int user_in_group(const char *username, const char *groupname);
#define SMART_LOG_ERROR 0
#define SMART_LOG_WARNING 1
#define SMART_LOG_INFO 2
#define SMART_LOG_DEBUG 3
#define SMART_LOG_TRACE 4
typedef struct {
char log_path[512];
char debug_level[64];
char admin_group[256];
int level;
} smart_helper_config_t;
static void trim(char *s)
{
char *p = s;
size_t len;
while (*p && isspace((unsigned char)*p)) {
p++;
}
if (p != s) {
memmove(s, p, strlen(p) + 1);
}
len = strlen(s);
while (len > 0 && isspace((unsigned char)s[len - 1])) {
s[len - 1] = '\0';
len--;
}
}
static void strip_quotes(char *s)
{
size_t len = strlen(s);
if (len >= 2) {
if ((s[0] == '\'' && s[len - 1] == '\'') ||
(s[0] == '"' && s[len - 1] == '"')) {
memmove(s, s + 1, len - 2);
s[len - 2] = '\0';
}
}
}
static int parse_perl_assignment(const char *line, char *key, size_t ksz, char *val, size_t vsz)
{
const char *p = line;
size_t ki = 0;
size_t vi = 0;
while (*p && isspace((unsigned char)*p)) {
p++;
}
if (*p != '$') {
return 0;
}
p++;
while (*p && (isalnum((unsigned char)*p) || *p == '_')) {
if (ki + 1 < ksz) {
key[ki++] = *p;
}
p++;
}
key[ki] = '\0';
while (*p && isspace((unsigned char)*p)) {
p++;
}
if (*p != '=') {
return 0;
}
p++;
while (*p && isspace((unsigned char)*p)) {
p++;
}
while (*p && *p != ';' && *p != '\n' && *p != '\r') {
if (vi + 1 < vsz) {
val[vi++] = *p;
}
p++;
}
val[vi] = '\0';
trim(key);
trim(val);
strip_quotes(val);
return key[0] != '\0';
}
static int parse_log_level(const char *value)
{
char buf[64];
size_t i;
if (value == NULL || value[0] == '\0') {
return SMART_LOG_INFO;
}
snprintf(buf, sizeof(buf), "%s", value);
trim(buf);
for (i = 0; buf[i]; i++) {
buf[i] = (char)tolower((unsigned char)buf[i]);
}
if (strcmp(buf, "error") == 0 || strcmp(buf, "err") == 0 || strcmp(buf, "0") == 0) {
return SMART_LOG_ERROR;
}
if (strcmp(buf, "warning") == 0 || strcmp(buf, "warn") == 0 || strcmp(buf, "1") == 0) {
return SMART_LOG_WARNING;
}
if (strcmp(buf, "info") == 0 || strcmp(buf, "2") == 0) {
return SMART_LOG_INFO;
}
if (strcmp(buf, "debug") == 0 || strcmp(buf, "3") == 0) {
return SMART_LOG_DEBUG;
}
if (strcmp(buf, "trace") == 0 || strcmp(buf, "4") == 0) {
return SMART_LOG_TRACE;
}
return SMART_LOG_INFO;
}
static const char *level_name(int level)
{
if (level <= SMART_LOG_ERROR) {
return "ERROR";
}
if (level == SMART_LOG_WARNING) {
return "WARNING";
}
if (level == SMART_LOG_DEBUG) {
return "DEBUG";
}
if (level >= SMART_LOG_TRACE) {
return "TRACE";
}
return "INFO";
}
static void smart_cfg_init(smart_helper_config_t *cfg)
{
memset(cfg, 0, sizeof(*cfg));
snprintf(cfg->log_path, sizeof(cfg->log_path), "%s", DEFAULT_SMART_LOG_PATH);
snprintf(cfg->debug_level, sizeof(cfg->debug_level), "%s", DEFAULT_SMART_LOG_LEVEL);
snprintf(cfg->admin_group, sizeof(cfg->admin_group), "%s", "root");
cfg->level = parse_log_level(cfg->debug_level);
}
static void smart_cfg_load(smart_helper_config_t *cfg, const char *path)
{
FILE *fh;
char line[2048];
if (path == NULL || path[0] == '\0') {
return;
}
fh = fopen(path, "r");
if (fh == NULL) {
return;
}
while (fgets(line, sizeof(line), fh) != NULL) {
char key[256];
char val[1024];
if (!parse_perl_assignment(line, key, sizeof(key), val, sizeof(val))) {
continue;
}
if (strcmp(key, "smart_log_path") == 0) {
snprintf(cfg->log_path, sizeof(cfg->log_path), "%s", val);
} else if (strcmp(key, "smart_debug_level") == 0 ||
strcmp(key, "smart_log_level") == 0) {
snprintf(cfg->debug_level, sizeof(cfg->debug_level), "%s", val);
cfg->level = parse_log_level(val);
} else if (strcmp(key, "smart_admin_group") == 0) {
snprintf(cfg->admin_group, sizeof(cfg->admin_group), "%s", val);
}
}
fclose(fh);
}
static void helper_log(smart_helper_config_t *cfg, const char *component, int level, const char *fmt, ...)
{
FILE *fh = stderr;
int close_fh = 0;
time_t now;
struct tm tm_now;
char tbuf[64];
va_list ap;
if (cfg != NULL && level > cfg->level) {
return;
}
if (cfg != NULL && cfg->log_path[0] != '\0') {
fh = fopen(cfg->log_path, "a");
if (fh != NULL) {
close_fh = 1;
} else {
fh = stderr;
}
}
now = time(NULL);
localtime_r(&now, &tm_now);
strftime(tbuf, sizeof(tbuf), "%Y-%m-%d %H:%M:%S", &tm_now);
fprintf(fh, "[%s] [%s] [SMArT helper] [%s] ", tbuf, level_name(level), component);
va_start(ap, fmt);
vfprintf(fh, fmt, ap);
va_end(ap);
fputc('\n', fh);
fflush(fh);
if (close_fh) {
fclose(fh);
}
}
static struct pam_conv conv = { static struct pam_conv conv = {
my_conv, my_conv,
@@ -38,35 +272,160 @@ char *pass;
int main( int argc, char **argv ) int main( int argc, char **argv )
{ {
pam_handle_t *pamh; pam_handle_t *pamh = NULL;
int retval, st = 1; int retval, st = 1;
const char *admin_group;
const char *smart_conf = DEFAULT_SMART_CONF;
smart_helper_config_t cfg;
smart_cfg_init(&cfg);
if( argc < 4 )
{
fprintf( stderr, "Usage: %s <user> <password> <admin-group> [smart.conf]\n", argv[0] );
return( 3 );
}
user = argv[1]; user = argv[1];
pass = argv[2]; pass = argv[2];
admin_group = argv[3];
if (argc >= 5 && argv[4] != NULL && argv[4][0] != '\0') {
smart_conf = argv[4];
}
smart_cfg_load(&cfg, smart_conf);
if (admin_group == NULL || admin_group[0] == '\0' || strcmp(admin_group, "-") == 0) {
admin_group = cfg.admin_group;
}
if( user == NULL || user[0] == '\0' ||
pass == NULL ||
admin_group == NULL || admin_group[0] == '\0' )
{
helper_log(&cfg, "check_login", SMART_LOG_ERROR, "invalid helper arguments");
return( 3 );
}
helper_log(&cfg, "check_login", SMART_LOG_INFO, "authentication requested user='%s' admin_group='%s'", user, admin_group);
retval = pam_start( "smart", user, &conv, &pamh ); retval = pam_start( "smart", user, &conv, &pamh );
if ( retval == PAM_SUCCESS ) if( retval == PAM_SUCCESS )
retval = pam_authenticate( pamh, PAM_SILENT ); retval = pam_authenticate( pamh, PAM_SILENT );
if ( retval == PAM_SUCCESS ) if( retval == PAM_SUCCESS )
st = retval = pam_acct_mgmt( pamh, PAM_SILENT ); st = retval = pam_acct_mgmt( pamh, PAM_SILENT );
if ( pam_end( pamh, retval ) != PAM_SUCCESS )
return( 1 );
return( st != PAM_SUCCESS ); if( pamh != NULL && pam_end( pamh, retval ) != PAM_SUCCESS ) {
helper_log(&cfg, "check_login", SMART_LOG_ERROR, "pam_end failed user='%s'", user);
return( 1 );
}
if( st != PAM_SUCCESS ) {
helper_log(&cfg, "check_login", SMART_LOG_WARNING, "pam authentication failed user='%s' pam_status=%d", user, st);
return( 1 );
}
helper_log(&cfg, "check_login", SMART_LOG_DEBUG, "pam authentication ok user='%s'", user);
if( ! user_in_group( user, admin_group ) ) {
helper_log(&cfg, "check_login", SMART_LOG_WARNING, "group authorization failed user='%s' required_group='%s'", user, admin_group);
return( 2 );
}
helper_log(&cfg, "check_login", SMART_LOG_INFO, "login accepted user='%s' required_group='%s'", user, admin_group);
return( 0 );
}
static int user_in_group(const char *username, const char *groupname)
{
struct passwd *pw;
struct group *gr;
int ngroups = 0;
gid_t *groups;
int i;
if( username == NULL || username[0] == '\0' ||
groupname == NULL || groupname[0] == '\0' )
{
return( 0 );
}
pw = getpwnam( username );
gr = getgrnam( groupname );
if( pw == NULL || gr == NULL )
{
return( 0 );
}
if( pw->pw_gid == gr->gr_gid )
{
return( 1 );
}
#if defined(__linux__) || defined(__GLIBC__)
getgrouplist( username, pw->pw_gid, NULL, &ngroups );
if( ngroups > 0 )
{
groups = calloc( (size_t) ngroups, sizeof( gid_t ) );
if( groups != NULL )
{
if( getgrouplist( username, pw->pw_gid, groups, &ngroups ) >= 0 )
{
for( i = 0; i < ngroups; i++ )
{
if( groups[i] == gr->gr_gid )
{
free( groups );
return( 1 );
}
}
}
free( groups );
}
}
#endif
if( gr->gr_mem != NULL )
{
for( i = 0; gr->gr_mem[i] != NULL; i++ )
{
if( strcmp( gr->gr_mem[i], username ) == 0 )
{
return( 1 );
}
}
}
return( 0 );
} }
int my_conv(int num_msg, const struct pam_message **msg, struct pam_response **resp, void *appdata_ptr) int my_conv(int num_msg, const struct pam_message **msg, struct pam_response **resp, void *appdata_ptr)
{ {
struct pam_response *reply; struct pam_response *reply;
int i; int i;
reply = (struct pam_response *) calloc( num_msg, sizeof( struct pam_response ) ); (void) msg;
(void) appdata_ptr;
reply = (struct pam_response *) calloc( (size_t) num_msg, sizeof( struct pam_response ) );
if( reply == NULL )
{
return( PAM_BUF_ERR );
}
for( i = 0; i < num_msg; i ++ ) for( i = 0; i < num_msg; i ++ )
{ {
reply[i].resp = (char *) strdup( pass ); /* Just give the password... It's all we know */ reply[i].resp = (char *) strdup( pass );
reply[i].resp_retcode = 0; reply[i].resp_retcode = 0;
} }
*resp = reply; *resp = reply;
return( PAM_SUCCESS ); return( PAM_SUCCESS );
} }

View File

@@ -12,9 +12,14 @@
#define LOG_PATH_DEFAULT "@MARS_NWE_LOG_DIR@/nwwebui.log" #define LOG_PATH_DEFAULT "@MARS_NWE_LOG_DIR@/nwwebui.log"
#define DEFAULT_SMART_LOG_PATH "@MARS_NWE_LOG_DIR@/smart.log"
#define DEFAULT_SMART_LOG_LEVEL "info"
#define LOG_LEVEL_ERROR 0 #define LOG_LEVEL_ERROR 0
#define LOG_LEVEL_INFO 1 #define LOG_LEVEL_WARNING 1
#define LOG_LEVEL_DEBUG 2 #define LOG_LEVEL_INFO 2
#define LOG_LEVEL_DEBUG 3
#define LOG_LEVEL_TRACE 4
#define LOG_LEVEL_DEFAULT LOG_LEVEL_INFO #define LOG_LEVEL_DEFAULT LOG_LEVEL_INFO
#define DEFAULT_BIND_IP "0.0.0.0" #define DEFAULT_BIND_IP "0.0.0.0"

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

BIN
doc/screenshots/login.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 467 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1005 KiB

View File

@@ -91,12 +91,86 @@ static void log_reopen(const char *path)
log_open(); log_open();
} }
static int parse_log_level(const char *value)
{
char buf[64];
size_t i;
if (!value || !*value) {
return LOG_LEVEL_DEFAULT;
}
while (*value && isspace((unsigned char)*value)) {
value++;
}
snprintf(buf, sizeof(buf), "%s", value);
for (i = 0; buf[i]; i++) {
buf[i] = (char)tolower((unsigned char)buf[i]);
}
while (i > 0 && isspace((unsigned char)buf[i - 1])) {
buf[i - 1] = '\0';
i--;
}
if (strcmp(buf, "error") == 0 || strcmp(buf, "err") == 0 || strcmp(buf, "0") == 0) {
return LOG_LEVEL_ERROR;
}
if (strcmp(buf, "warning") == 0 || strcmp(buf, "warn") == 0 || strcmp(buf, "1") == 0) {
return LOG_LEVEL_WARNING;
}
if (strcmp(buf, "info") == 0 || strcmp(buf, "2") == 0) {
return LOG_LEVEL_INFO;
}
if (strcmp(buf, "debug") == 0 || strcmp(buf, "3") == 0) {
return LOG_LEVEL_DEBUG;
}
if (strcmp(buf, "trace") == 0 || strcmp(buf, "4") == 0) {
return LOG_LEVEL_TRACE;
}
/*
Compatibility with the old numeric values:
0 = error
1 = info
2 = debug
Named levels are preferred for new configurations.
*/
if (strcmp(buf, "old-info") == 0) {
return LOG_LEVEL_INFO;
}
return LOG_LEVEL_DEFAULT;
}
static const char *log_level_name(int level)
{
if (level <= LOG_LEVEL_ERROR) {
return "ERROR";
}
if (level == LOG_LEVEL_WARNING) {
return "WARNING";
}
if (level == LOG_LEVEL_DEBUG) {
return "DEBUG";
}
if (level >= LOG_LEVEL_TRACE) {
return "TRACE";
}
return "INFO";
}
static void log_msg(int level, const char *fmt, ...) static void log_msg(int level, const char *fmt, ...)
{ {
time_t now; time_t now;
struct tm tm_now; struct tm tm_now;
char tbuf[64]; char tbuf[64];
const char *lvl = "INFO"; const char *lvl;
va_list ap; va_list ap;
if (level > g_log_level) { if (level > g_log_level) {
@@ -109,11 +183,7 @@ static void log_msg(int level, const char *fmt, ...)
localtime_r(&now, &tm_now); localtime_r(&now, &tm_now);
strftime(tbuf, sizeof(tbuf), "%Y-%m-%d %H:%M:%S", &tm_now); strftime(tbuf, sizeof(tbuf), "%Y-%m-%d %H:%M:%S", &tm_now);
if (level == LOG_LEVEL_ERROR) { lvl = log_level_name(level);
lvl = "ERROR";
} else if (level == LOG_LEVEL_DEBUG) {
lvl = "DEBUG";
}
fprintf(g_log_fp, "[%s] [%s] ", tbuf, lvl); fprintf(g_log_fp, "[%s] [%s] ", tbuf, lvl);
@@ -471,7 +541,7 @@ static void load_smart_conf(nw_config_t *cfg)
if (strcmp(key, "nw_bind_ip") == 0) { if (strcmp(key, "nw_bind_ip") == 0) {
snprintf(cfg->bind_ip, sizeof(cfg->bind_ip), "%s", val); snprintf(cfg->bind_ip, sizeof(cfg->bind_ip), "%s", val);
} else if (strcmp(key, "nw_log_level") == 0) { } else if (strcmp(key, "nw_log_level") == 0) {
g_log_level = atoi(val); g_log_level = parse_log_level(val);
} else if (strcmp(key, "nw_log_file") == 0) { } else if (strcmp(key, "nw_log_file") == 0) {
snprintf(cfg->log_file, sizeof(cfg->log_file), "%s", val); snprintf(cfg->log_file, sizeof(cfg->log_file), "%s", val);
} else if (strcmp(key, "nw_ssl_enable") == 0) { } else if (strcmp(key, "nw_ssl_enable") == 0) {

View File

@@ -122,6 +122,49 @@ sub delconfigline( $ )
@conf = grep( !/^$_[0] /i, grep( !/^$_[0]$/i, @conf ) ); @conf = grep( !/^$_[0] /i, grep( !/^$_[0]$/i, @conf ) );
} }
sub queue_config_name_from_line( $ )
{
my $line = $_[0];
$line = '' unless defined( $line );
$line =~ s/[\r\n]//g;
$line =~ s/#.*//;
$line =~ s/^\s*21\s+//i;
$line =~ s/^\s+//;
$line =~ s/\s+$//;
return ( split( /\s+/, $line, 2 ) )[0];
}
sub delconfigqueue( $ )
{
my $name = $_[0];
my $conf_before = scalar( @conf );
my $raw_before = scalar( @rawconf );
$name = '' unless defined( $name );
$name =~ s/^\s+//;
$name =~ s/\s+$//;
$name = uc( $name );
return( 0, 0 ) if $name eq '';
@conf = grep {
my $qname = queue_config_name_from_line( $_ );
!( $_ =~ /^\s*21(?:\s|$)/i && uc( $qname ) eq $name )
} @conf;
@rawconf = grep {
my $raw = $_;
my $qname = queue_config_name_from_line( $raw );
!( $raw =~ /^\s*21(?:\s|$)/i && uc( $qname ) eq $name )
} @rawconf;
return( $conf_before - scalar( @conf ), $raw_before - scalar( @rawconf ) );
}
sub normalize_line( $ ) sub normalize_line( $ )
{ {
my $x = $_[0]; my $x = $_[0];
@@ -207,12 +250,17 @@ sub writeconfig_compact()
sub writeconfig_markers() sub writeconfig_markers()
{ {
my( %secmap, %emitted ); my( %secmap, %emitted, %managed );
my( $line, $active_key, $inside_active ); my( $line, $active_key, $inside_active, $sec, $key );
%secmap = build_marker_map(); %secmap = build_marker_map();
$inside_active = ''; $inside_active = '';
foreach $key ( keys( %secmap ) )
{
$managed{$key} = 1;
}
open( CONF, '>' . $mars_config ) or die "Could not write $mars_config: $!"; open( CONF, '>' . $mars_config ) or die "Could not write $mars_config: $!";
foreach $line ( @rawconf ) foreach $line ( @rawconf )
@@ -249,9 +297,40 @@ sub writeconfig_markers()
next; next;
} }
$sec = section_of_line( $line );
if( $sec ne '' )
{
$key = grouped_section_key( $sec );
if( $managed{$key} )
{
# This section was changed in @conf. Do not write the stale
# raw line again from @rawconf. If the section has no ACTIVE
# marker, it is emitted once after the raw file has been copied.
next;
}
}
print CONF $line; print CONF $line;
} }
foreach $key ( sort {
my $aa = $a; my $bb = $b;
$aa =~ s/-.*//; $bb =~ s/-.*//;
$aa <=> $bb
} keys( %secmap ) )
{
next if $emitted{$key};
print CONF "\n";
foreach my $entry ( @{ $secmap{$key} } )
{
print CONF $entry . "\n";
}
$emitted{$key} = 1;
}
close( CONF ); close( CONF );
} }

View File

@@ -327,6 +327,51 @@ sub sanitize_cups_printer_name( $ )
return $s; return $s;
} }
sub queue_config_fields( $ )
{
my $line = $_[0];
$line = '' unless defined( $line );
$line =~ s/^\s*21\s+//i;
$line =~ s/^\s+//;
$line =~ s/\s+$//;
my( $name, $spool, $cmd ) = split( /\s+/, $line, 3 );
$name = '' unless defined( $name );
$spool = '' unless defined( $spool );
$cmd = '' unless defined( $cmd );
return( $name, $spool, $cmd );
}
sub queue_config_by_name( $ )
{
my $wanted = uc( $_[0] );
foreach my $line ( getconfig( 21 ) )
{
my( $name, $spool, $cmd ) = queue_config_fields( $line );
return( $spool, $cmd ) if uc( $name ) eq $wanted;
}
return( '', '' );
}
sub queue_names_from_config()
{
my @names = ();
foreach my $line ( getconfig( 21 ) )
{
my( $name, $spool, $cmd ) = queue_config_fields( $line );
push( @names, $name ) if $name ne '';
}
return @names;
}
sub cups_queue_name( $ ) sub cups_queue_name( $ )
{ {
my $s = sanitize_cups_printer_name( $_[0] ); my $s = sanitize_cups_printer_name( $_[0] );
@@ -794,6 +839,424 @@ sub smart_group_checkbox_rows( $ )
} }
sub volume_name_from_path( $ )
{
my $path = $_[0];
$path = '' unless defined( $path );
$path =~ s#/*$##;
$path =~ s#^.*/##;
$path = 'ROOT' if $path eq '';
$path = uc( $path );
$path =~ s/[^A-Z0-9_\-]/_/g;
$path = substr( $path, 0, 15 );
$path = 'VOLUME' if $path eq '';
return $path;
}
sub volume_mountpoint_is_useful( $$ )
{
my( $mountpoint, $fstype ) = @_;
return 0 if ! defined( $mountpoint ) || $mountpoint eq '';
return 0 if $mountpoint eq '/';
# Pseudo and system trees are not useful as NetWare volumes.
return 0 if $mountpoint =~ m#^/(proc|sys|dev|run)(/|$)#;
return 0 if $mountpoint =~ m#^/(tmp|var/tmp)(/|$)#;
return 0 if $mountpoint =~ m#^/boot(/|$)#;
return 0 if $mountpoint =~ m#^/(etc|usr|bin|sbin|lib|lib64)(/|$)#;
return 0 if $mountpoint =~ m#^/system(/|$)#;
# Do not require -d here. The webui service may have /home masked by
# systemd hardening, while the real MARS_NWE service can still use it.
return 1;
}
sub local_mountpoints()
{
my @mounts = ();
my %seen = ();
my $add_mount = sub
{
my( $mountpoint, $fstype, $source ) = @_;
$mountpoint = '' unless defined( $mountpoint );
$fstype = '' unless defined( $fstype );
$source = '' unless defined( $source );
$mountpoint =~ s#\\040# #g;
$mountpoint =~ s#\\011#\t#g;
$mountpoint =~ s#\\012#\n#g;
$mountpoint =~ s#\\134#\\#g;
return if $mountpoint eq '';
return if $mountpoint =~ /[\r\n\t]/;
return if $mountpoint =~ /'/;
return if $seen{$mountpoint};
return if $fstype =~ /^(proc|sysfs|devtmpfs|devpts|tmpfs|securityfs|cgroup|cgroup2|pstore|bpf|tracefs|debugfs|configfs|fusectl|mqueue|hugetlbfs|autofs|overlay|squashfs|portal|binfmt_misc|efivarfs)$/;
return if ! volume_mountpoint_is_useful( $mountpoint, $fstype );
push( @mounts, {
path => $mountpoint,
fstype => $fstype ne '' ? $fstype : 'mount',
source => $source ne '' ? $source : 'mount',
name => volume_name_from_path( $mountpoint ),
} );
$seen{$mountpoint} = 1;
};
foreach my $file ( '/proc/1/mountinfo', '/proc/self/mountinfo' )
{
next if ! open( my $fh, '<', $file );
while( my $line = <$fh> )
{
chomp( $line );
my @parts = split( / - /, $line, 2 );
next if scalar( @parts ) != 2;
my @left = split( /\s+/, $parts[0] );
my @right = split( /\s+/, $parts[1] );
next if scalar( @left ) < 5;
next if scalar( @right ) < 3;
$add_mount->( $left[4], $right[0], $right[1] );
}
close( $fh );
}
if( open( my $fh, '<', '/proc/mounts' ) )
{
while( my $line = <$fh> )
{
chomp( $line );
my( $source, $mountpoint, $fstype ) = split( /\s+/, $line );
$add_mount->( $mountpoint, $fstype, $source );
}
close( $fh );
}
@mounts = sort { $a->{path} cmp $b->{path} } @mounts;
return @mounts;
}
sub volume_mount_import_rows()
{
my $html = '';
my @mounts = local_mountpoints();
my %existing = ();
foreach my $line ( getconfig( 1 ) )
{
my @f = split( ' ', $line );
$existing{lc( $f[1] )} = 1 if defined( $f[1] ) && $f[1] ne '';
$existing{'path:' . $f[2]} = 1 if defined( $f[2] ) && $f[2] ne '';
}
if( scalar( @mounts ) == 0 )
{
return qq|\t<TR BGCOLOR="#ece0cf">\n\t\t<TD COLSPAN=2><B>Local mountpoints detected on host</B></TD>\n\t</TR>\n\t<TR BGCOLOR="#fbf7f1">\n\t\t<TD COLSPAN=2><SMALL>No suitable local mountpoints were found.</SMALL></TD>\n\t</TR>\n|;
}
$html .= qq|\t<TR BGCOLOR="#ece0cf">\n\t\t<TD COLSPAN=2><B>Local mountpoints detected on host</B></TD>\n\t</TR>\n|;
foreach my $m ( @mounts )
{
my $name = $m->{name};
my $path = $m->{path};
my $fstype = $m->{fstype};
my $source = $m->{source};
my $exists = $existing{lc( $name )} || $existing{'path:' . $path};
$html .= "\t<TR BGCOLOR=\"#fbf7f1\">\n";
$html .= "\t\t<TD><TT>" . html_escape( $path ) . "</TT><BR><SMALL>Volume: <TT>" . html_escape( $name ) . "</TT> &nbsp; Type: <TT>" . html_escape( $fstype ) . "</TT> &nbsp; Source: <TT>" . html_escape( $source ) . "</TT></SMALL></TD>\n";
if( $exists )
{
$html .= "\t\t<TD ALIGN=RIGHT><SMALL>already configured</SMALL></TD>\n";
}
else
{
my $href = '/settings/volumes/add_new?mount_path=' . url_escape( $path ) . '&path=' . url_escape( $path );
$html .= "\t\t<TD ALIGN=RIGHT><A HREF=\"" . html_escape( $href ) . "\">Add as volume</A></TD>\n";
}
$html .= "\t</TR>\n";
}
return $html;
}
sub volume_mount_defaults_from_query()
{
my %defaults = ();
$defaults{name} = '';
$defaults{path} = '';
my $path = '';
if( defined( $p{mount_path} ) && $p{mount_path} ne '' )
{
$path = $p{mount_path};
}
elsif( defined( $p{path} ) && $p{path} ne '' )
{
$path = $p{path};
}
if( $path ne '' )
{
$path =~ s/[\r\n\t']//g;
$path =~ s#/{2,}#/#g;
$path =~ s#/$## if length( $path ) > 1;
if( volume_mountpoint_is_useful( $path, 'mount' ) )
{
$defaults{path} = $path;
$defaults{name} = volume_name_from_path( $path );
}
}
return %defaults;
}
sub mars_nwe_service_active()
{
my $service = defined( $mars_nwe_service ) && $mars_nwe_service ne '' ? $mars_nwe_service : 'mars-nwe-serv.service';
my $systemctl = defined( $smart_systemctl_path ) && $smart_systemctl_path ne '' ? $smart_systemctl_path : '/usr/bin/systemctl';
return 0 if ! -x $systemctl;
my $rc = system( $systemctl, 'is-active', '--quiet', $service );
return $rc == 0 ? 1 : 0;
}
sub mars_nwe_service_guard_rows( $ )
{
my $what = $_[0];
$what = 'this section' unless defined( $what ) && $what ne '';
return '' if mars_nwe_service_active();
return qq|\t<TR BGCOLOR="#fff3e0">\n|
. qq|\t\t<TD COLSPAN=2><B>MARS_NWE service is not running.</B><BR>\n|
. qq|\t\t<SMALL>Start <TT>mars-nwe-serv.service</TT> before managing | . html_escape( $what ) . qq|. |
. qq|<A HREF="/static/start.html" TARGET="OPTS">Open service page</A></SMALL></TD>\n|
. qq|\t</TR>\n|;
}
sub settings_message_html()
{
return '' if ! defined( $p{msg} ) || $p{msg} eq '';
my $msg = $p{msg};
$msg =~ s/\+/ /g;
return qq|<DIV CLASS="smart-message">| . html_escape( $msg ) . qq|</DIV>\n|;
}
sub user_group_checkbox_rows( $ )
{
my $user = $_[0];
my $html = '';
my %selected = ();
$user = '' unless defined( $user );
if( $user ne '' && $user ne 'add_new' )
{
my $server = get_server();
my $groups = `nwbpvalues -S $server -t 1 -o $user -p "GROUPS_I'M_IN" 2>/dev/null`;
foreach my $line ( split( /\n/, $groups ) )
{
if( $line =~ /([A-Za-z0-9_\-]+)/ )
{
$selected{uc( $1 )} = 1;
}
}
}
my @groups = getconfig( 12 );
my @names = ();
foreach my $g ( @groups )
{
my @f = split( /\s+/, $g );
next if ! defined( $f[1] ) || $f[1] eq '';
push( @names, uc( $f[1] ) );
}
push( @names, 'EVERYONE' ) if ! grep { $_ eq 'EVERYONE' } @names;
@names = sort @names;
foreach my $g ( @names )
{
my $checked = ( $selected{$g} || ( $g eq 'EVERYONE' && $user eq 'add_new' ) ) ? ' CHECKED' : '';
$html .= qq|<LABEL STYLE="margin-left:10px;white-space:nowrap;">| . html_escape( $g )
. qq| <INPUT NAME="group_| . html_escape( $g ) . qq|" TYPE=CHECKBOX$checked></LABEL>\n|;
}
return $html;
}
sub current_bindery_unix_user( $ )
{
# Do not query the live bindery while rendering the edit form.
# Some nwbpvalues versions can block or print unexpected data here.
# Existing UNIX_USER stays unchanged unless the admin selects a value.
return '';
}
sub smart_unix_users_for_select()
{
my @users = ();
my %seen = ();
my $helper = defined( $smart_userlist_path ) && $smart_userlist_path ne '' ? $smart_userlist_path : '@MARS_NWE_INSTALL_FULL_LIBEXECDIR@/smart_userlist';
my $conf_path = defined( $smart_conf_path ) && $smart_conf_path ne '' ? $smart_conf_path : '@MARS_NWE_INSTALL_FULL_CONFDIR@/smart.conf';
if( -x $helper && open( my $fh, '-|', $helper, '--config', $conf_path ) )
{
while( my $line = <$fh> )
{
chomp( $line );
my( $name, $uid, $gid, $gecos, $home, $shell ) = split( /\t/, $line );
next if ! defined( $name ) || $name eq '';
next if $seen{$name};
push( @users, {
name => $name,
uid => defined( $uid ) ? $uid : '',
fullname => defined( $gecos ) ? $gecos : '',
} );
$seen{$name} = 1;
}
close( $fh );
}
# Fallback: getent passwd. This is only used when smart_userlist is not
# available or returned nothing.
if( scalar( @users ) == 0 && open( my $fh, '-|', 'getent', 'passwd' ) )
{
while( my $line = <$fh> )
{
chomp( $line );
my( $name, undef, $uid, $gid, $gecos, $home, $shell ) = split( /:/, $line );
next if ! defined( $name ) || $name eq '';
next if defined( $uid ) && $uid ne '' && $uid < 1000;
next if defined( $shell ) && $shell =~ m#/(false|nologin)$#;
next if $seen{$name};
push( @users, {
name => $name,
uid => defined( $uid ) ? $uid : '',
fullname => defined( $gecos ) ? $gecos : '',
} );
$seen{$name} = 1;
}
close( $fh );
}
@users = sort { $a->{name} cmp $b->{name} } @users;
return @users;
}
sub unix_user_select_options( $ )
{
my $selected = $_[0];
my $html = '';
$selected = '' unless defined( $selected );
foreach my $u ( smart_unix_users_for_select() )
{
next if ! defined( $u ) || ref( $u ) ne 'HASH';
my $name = $u->{name};
next if ! defined( $name ) || $name eq '';
my $sel = $name eq $selected ? ' SELECTED' : '';
$html .= '<OPTION VALUE="' . html_escape( $name ) . '"' . $sel . '>' . html_escape( $name ) . "</OPTION>
";
}
if( $selected ne '' && $html !~ /VALUE="\Q$selected\E"/ )
{
$html = '<OPTION VALUE="' . html_escape( $selected ) . '" SELECTED>' . html_escape( $selected ) . "</OPTION>\n" . $html;
}
return $html;
}
sub user_unix_mapping_row( $$ )
{
my( $user, $selected ) = @_;
$user = '' unless defined( $user );
$selected = '' unless defined( $selected );
my $unix_user_options = unix_user_select_options( $selected );
if( $user eq 'add_new' )
{
return qq| <TR BGCOLOR="#fbf7f1">\n|
. qq|\t\t<TD><B>UNIX user:</B></TD>\n|
. qq|\t\t<TD ALIGN=RIGHT>\n|
. qq|\t\t\t<SELECT NAME="unix_user" SIZE=5 STYLE="width:200px;max-width:200px;">\n|
. qq|\t\t\t\t<OPTION VALUE=""></OPTION>\n|
. $unix_user_options
. qq|\t\t\t</SELECT><BR>\n|
. qq|\t\t</TD>\n|
. qq|\t</TR>\n|;
}
my $display = $selected ne '' ? $selected : '(unchanged)';
return qq| <TR BGCOLOR="#fbf7f1">\n|
. qq|\t\t<TD><B>UNIX user:</B></TD>\n|
. qq|\t\t<TD ALIGN=RIGHT><TT>| . html_escape( $display ) . qq|</TT><BR>\n|
. qq|\t\t\t<LABEL><INPUT TYPE=CHECKBOX NAME="change_unix_user" VALUE="1" onclick="document.getElementById('unix_user_change_box').style.display=this.checked?'block':'none'"> Change UNIX user mapping</LABEL>\n|
. qq|\t\t\t<DIV ID="unix_user_change_box" STYLE="display:none;margin-top:8px;">\n|
. qq|\t\t\t\t<SELECT NAME="unix_user" SIZE=5 STYLE="width:200px;max-width:200px;">\n|
. qq|\t\t\t\t\t<OPTION VALUE=""></OPTION>\n|
. $unix_user_options
. qq|\t\t\t\t</SELECT>\n|
. qq|\t\t\t</DIV>\n|
. qq|\t\t</TD>\n|
. qq|\t</TR>\n|;
}
sub handle_request() sub handle_request()
{ {
if( $c[1] eq 'general' ) if( $c[1] eq 'general' )
@@ -849,12 +1312,13 @@ INPUT[type=SUBMIT], INPUT[type=RESET], INPUT[type=BUTTON] {
} }
A { color:#9f2f26; } A { color:#9f2f26; }
TT { color:#5b4b38; } TT { color:#5b4b38; }
.smart-message { margin:0 0 12px 0; padding:10px 12px; border:1px solid #b8d9a8; border-radius:12px; background:#edf7e8; color:#2f5b24; font-weight:bold; }
</style> </style>
</HEAD> </HEAD>
<BODY BGCOLOR="#f6f2ea"> <BODY BGCOLOR="#f6f2ea">
$settings_nav_bar $settings_nav_bar
<FORM ACTION="/apply/general" METHOD=GET> @{[ settings_message_html() ]}<FORM ACTION="/apply/general" METHOD=GET>
<TABLE BORDER=0 CELLSPACING=0 WIDTH=100%> <TABLE BORDER=0 CELLSPACING=0 WIDTH=100%>
<TR BGCOLOR="#d7c0a0"> <TR BGCOLOR="#d7c0a0">
<TD> <TD>
@@ -1008,12 +1472,13 @@ INPUT[type=SUBMIT], INPUT[type=RESET], INPUT[type=BUTTON] {
} }
A { color:#9f2f26; } A { color:#9f2f26; }
TT { color:#5b4b38; } TT { color:#5b4b38; }
.smart-message { margin:0 0 12px 0; padding:10px 12px; border:1px solid #b8d9a8; border-radius:12px; background:#edf7e8; color:#2f5b24; font-weight:bold; }
</style> </style>
</HEAD> </HEAD>
<BODY BGCOLOR="#f6f2ea"> <BODY BGCOLOR="#f6f2ea">
$settings_nav_bar $settings_nav_bar
<FORM ACTION="/apply/dirs" METHOD=GET> @{[ settings_message_html() ]}<FORM ACTION="/apply/dirs" METHOD=GET>
<TABLE BORDER=0 CELLSPACING=0 WIDTH=100%> <TABLE BORDER=0 CELLSPACING=0 WIDTH=100%>
<TR BGCOLOR="#d7c0a0"> <TR BGCOLOR="#d7c0a0">
<TD> <TD>
@@ -1137,12 +1602,13 @@ INPUT[type=SUBMIT], INPUT[type=RESET], INPUT[type=BUTTON] {
} }
A { color:#9f2f26; } A { color:#9f2f26; }
TT { color:#5b4b38; } TT { color:#5b4b38; }
.smart-message { margin:0 0 12px 0; padding:10px 12px; border:1px solid #b8d9a8; border-radius:12px; background:#edf7e8; color:#2f5b24; font-weight:bold; }
</style> </style>
</HEAD> </HEAD>
<BODY BGCOLOR="#f6f2ea"> <BODY BGCOLOR="#f6f2ea">
$settings_nav_bar $settings_nav_bar
<FORM ACTION="/apply/configh" METHOD=GET> @{[ settings_message_html() ]}<FORM ACTION="/apply/configh" METHOD=GET>
<TABLE BORDER=0 CELLSPACING=0 WIDTH=100%> <TABLE BORDER=0 CELLSPACING=0 WIDTH=100%>
<TR BGCOLOR="#d7c0a0"> <TR BGCOLOR="#d7c0a0">
<TD> <TD>
@@ -1271,12 +1737,13 @@ INPUT[type=SUBMIT], INPUT[type=RESET], INPUT[type=BUTTON] {
} }
A { color:#9f2f26; } A { color:#9f2f26; }
TT { color:#5b4b38; } TT { color:#5b4b38; }
.smart-message { margin:0 0 12px 0; padding:10px 12px; border:1px solid #b8d9a8; border-radius:12px; background:#edf7e8; color:#2f5b24; font-weight:bold; }
</style> </style>
</HEAD> </HEAD>
<BODY BGCOLOR="#f6f2ea"> <BODY BGCOLOR="#f6f2ea">
$settings_nav_bar $settings_nav_bar
<FORM ACTION="/apply/security" METHOD=GET> @{[ settings_message_html() ]}<FORM ACTION="/apply/security" METHOD=GET>
<TABLE BORDER=0 CELLSPACING=0 WIDTH=100%> <TABLE BORDER=0 CELLSPACING=0 WIDTH=100%>
<TR BGCOLOR="#d7c0a0"> <TR BGCOLOR="#d7c0a0">
<TD> <TD>
@@ -1394,12 +1861,13 @@ INPUT[type=SUBMIT], INPUT[type=RESET], INPUT[type=BUTTON] {
} }
A { color:#9f2f26; } A { color:#9f2f26; }
TT { color:#5b4b38; } TT { color:#5b4b38; }
.smart-message { margin:0 0 12px 0; padding:10px 12px; border:1px solid #b8d9a8; border-radius:12px; background:#edf7e8; color:#2f5b24; font-weight:bold; }
</style> </style>
</HEAD> </HEAD>
<BODY BGCOLOR="#f6f2ea"> <BODY BGCOLOR="#f6f2ea">
$settings_nav_bar $settings_nav_bar
<FORM ACTION="/apply/susers" METHOD=GET> @{[ settings_message_html() ]}<FORM ACTION="/apply/susers" METHOD=GET>
<TABLE BORDER=0 CELLSPACING=0 WIDTH=100%> <TABLE BORDER=0 CELLSPACING=0 WIDTH=100%>
<TR BGCOLOR="#d7c0a0"> <TR BGCOLOR="#d7c0a0">
<TD> <TD>
@@ -1561,12 +2029,13 @@ INPUT[type=SUBMIT], INPUT[type=RESET], INPUT[type=BUTTON] {
} }
A { color:#9f2f26; } A { color:#9f2f26; }
TT { color:#5b4b38; } TT { color:#5b4b38; }
.smart-message { margin:0 0 12px 0; padding:10px 12px; border:1px solid #b8d9a8; border-radius:12px; background:#edf7e8; color:#2f5b24; font-weight:bold; }
</style> </style>
</HEAD> </HEAD>
<BODY BGCOLOR="#f6f2ea"> <BODY BGCOLOR="#f6f2ea">
$settings_nav_bar $settings_nav_bar
<FORM ACTION="/apply/logging" METHOD=GET> @{[ settings_message_html() ]}<FORM ACTION="/apply/logging" METHOD=GET>
<TABLE BORDER=0 CELLSPACING=0 WIDTH=100%> <TABLE BORDER=0 CELLSPACING=0 WIDTH=100%>
<TR BGCOLOR="#d7c0a0"> <TR BGCOLOR="#d7c0a0">
<TD> <TD>
@@ -1768,6 +2237,7 @@ EOF
} }
elsif( $c[1] eq 'volumes' ) elsif( $c[1] eq 'volumes' )
{ {
my $volume_mount_import_rows = volume_mount_import_rows();
if( $c[2] eq '' ) if( $c[2] eq '' )
{ {
print <<EOF; print <<EOF;
@@ -1803,12 +2273,13 @@ INPUT[type=SUBMIT], INPUT[type=RESET], INPUT[type=BUTTON] {
} }
A { color:#9f2f26; } A { color:#9f2f26; }
TT { color:#5b4b38; } TT { color:#5b4b38; }
.smart-message { margin:0 0 12px 0; padding:10px 12px; border:1px solid #b8d9a8; border-radius:12px; background:#edf7e8; color:#2f5b24; font-weight:bold; }
</style> </style>
</HEAD> </HEAD>
<BODY BGCOLOR="#f6f2ea"> <BODY BGCOLOR="#f6f2ea">
$settings_nav_bar $settings_nav_bar
<TABLE BORDER=0 CELLSPACING=0 WIDTH=100%> @{[ settings_message_html() ]}<TABLE BORDER=0 CELLSPACING=0 WIDTH=100%>
<TR BGCOLOR="#d7c0a0"> <TR BGCOLOR="#d7c0a0">
<TD> <TD>
<B><FONT SIZE=+2>Volumes</FONT></B> <B><FONT SIZE=+2>Volumes</FONT></B>
@@ -1836,6 +2307,7 @@ EOF
print <<EOF; print <<EOF;
<TR BGCOLOR="#d7c0a0"> <TR BGCOLOR="#d7c0a0">
<TD COLSPAN=2> <TD COLSPAN=2>
$volume_mount_import_rows
<B><A HREF="/settings/volumes/add_new">Add new volume</A></B> <B><A HREF="/settings/volumes/add_new">Add new volume</A></B>
</TD> </TD>
</TR> </TR>
@@ -1848,9 +2320,36 @@ EOF
} }
else else
{ {
$c = getconfigline( '1 ' . $c[2] ); my $volume_action = $c[2];
$c = getconfigline( '1 ' . $volume_action );
@c = split( ' ', $c ); @c = split( ' ', $c );
$c = ''; $c = '';
my %volume_mount_defaults = volume_mount_defaults_from_query();
if( $volume_action eq 'add_new' )
{
$c[0] = $volume_mount_defaults{name} if defined( $volume_mount_defaults{name} ) && $volume_mount_defaults{name} ne '';
$c[1] = $volume_mount_defaults{path} if defined( $volume_mount_defaults{path} ) && $volume_mount_defaults{path} ne '';
$c[2] = 'k' if ! defined( $c[2] ) || $c[2] eq '';
}
if( $volume_action eq 'add_new' )
{
my %volume_mount_defaults = volume_mount_defaults_from_query();
if( defined( $volume_mount_defaults{name} ) && $volume_mount_defaults{name} ne '' )
{
$c[0] = $volume_mount_defaults{name};
}
if( defined( $volume_mount_defaults{path} ) && $volume_mount_defaults{path} ne '' )
{
$c[1] = $volume_mount_defaults{path};
}
# Default new imported volumes to lower-case filenames.
$c[2] = 'k' if ! defined( $c[2] ) || $c[2] eq '';
}
$d = $c[2]; $d = $c[2];
$d =~ s/[^ik]//g; $d =~ s/[^ik]//g;
@@ -1928,12 +2427,13 @@ INPUT[type=SUBMIT], INPUT[type=RESET], INPUT[type=BUTTON] {
} }
A { color:#9f2f26; } A { color:#9f2f26; }
TT { color:#5b4b38; } TT { color:#5b4b38; }
.smart-message { margin:0 0 12px 0; padding:10px 12px; border:1px solid #b8d9a8; border-radius:12px; background:#edf7e8; color:#2f5b24; font-weight:bold; }
</style> </style>
</HEAD> </HEAD>
<BODY BGCOLOR="#f6f2ea"> <BODY BGCOLOR="#f6f2ea">
$settings_nav_bar $settings_nav_bar
<FORM ACTION="/apply/volumes/$c[0]" METHOD=GET> @{[ settings_message_html() ]}<FORM ACTION="/apply/volumes/$c[0]" METHOD=GET>
<TABLE BORDER=0 CELLSPACING=0 WIDTH=100%> <TABLE BORDER=0 CELLSPACING=0 WIDTH=100%>
<TR BGCOLOR="#d7c0a0"> <TR BGCOLOR="#d7c0a0">
<TD> <TD>
@@ -2100,12 +2600,13 @@ INPUT[type=SUBMIT], INPUT[type=RESET], INPUT[type=BUTTON] {
} }
A { color:#9f2f26; } A { color:#9f2f26; }
TT { color:#5b4b38; } TT { color:#5b4b38; }
.smart-message { margin:0 0 12px 0; padding:10px 12px; border:1px solid #b8d9a8; border-radius:12px; background:#edf7e8; color:#2f5b24; font-weight:bold; }
</style> </style>
</HEAD> </HEAD>
<BODY BGCOLOR="#f6f2ea"> <BODY BGCOLOR="#f6f2ea">
$settings_nav_bar $settings_nav_bar
<TABLE BORDER=0 CELLSPACING=0 WIDTH=100%> @{[ settings_message_html() ]}<TABLE BORDER=0 CELLSPACING=0 WIDTH=100%>
<TR BGCOLOR="#d7c0a0"> <TR BGCOLOR="#d7c0a0">
<TD> <TD>
<B><FONT SIZE=+2>Devices</FONT></B> <B><FONT SIZE=+2>Devices</FONT></B>
@@ -2216,12 +2717,13 @@ INPUT[type=SUBMIT], INPUT[type=RESET], INPUT[type=BUTTON] {
} }
A { color:#9f2f26; } A { color:#9f2f26; }
TT { color:#5b4b38; } TT { color:#5b4b38; }
.smart-message { margin:0 0 12px 0; padding:10px 12px; border:1px solid #b8d9a8; border-radius:12px; background:#edf7e8; color:#2f5b24; font-weight:bold; }
</style> </style>
</HEAD> </HEAD>
<BODY BGCOLOR="#f6f2ea"> <BODY BGCOLOR="#f6f2ea">
$settings_nav_bar $settings_nav_bar
<FORM ACTION="/apply/devices/$title" METHOD=GET> @{[ settings_message_html() ]}<FORM ACTION="/apply/devices/$title" METHOD=GET>
<TABLE BORDER=0 CELLSPACING=0 WIDTH=100%> <TABLE BORDER=0 CELLSPACING=0 WIDTH=100%>
<TR BGCOLOR="#d7c0a0"> <TR BGCOLOR="#d7c0a0">
<TD> <TD>
@@ -2327,12 +2829,13 @@ INPUT[type=SUBMIT], INPUT[type=RESET], INPUT[type=BUTTON] {
} }
A { color:#9f2f26; } A { color:#9f2f26; }
TT { color:#5b4b38; } TT { color:#5b4b38; }
.smart-message { margin:0 0 12px 0; padding:10px 12px; border:1px solid #b8d9a8; border-radius:12px; background:#edf7e8; color:#2f5b24; font-weight:bold; }
</style> </style>
</HEAD> </HEAD>
<BODY BGCOLOR="#f6f2ea"> <BODY BGCOLOR="#f6f2ea">
$settings_nav_bar $settings_nav_bar
<FORM ACTION="/apply/smart" METHOD=GET> @{[ settings_message_html() ]}<FORM ACTION="/apply/smart" METHOD=GET>
<TABLE BORDER=0 CELLSPACING=0 WIDTH=100%> <TABLE BORDER=0 CELLSPACING=0 WIDTH=100%>
<TR BGCOLOR="#d7c0a0"> <TR BGCOLOR="#d7c0a0">
<TD> <TD>
@@ -2441,12 +2944,13 @@ INPUT[type=SUBMIT], INPUT[type=RESET], INPUT[type=BUTTON] {
} }
A { color:#9f2f26; } A { color:#9f2f26; }
TT { color:#5b4b38; } TT { color:#5b4b38; }
.smart-message { margin:0 0 12px 0; padding:10px 12px; border:1px solid #b8d9a8; border-radius:12px; background:#edf7e8; color:#2f5b24; font-weight:bold; }
</style> </style>
</HEAD> </HEAD>
<BODY BGCOLOR="#f6f2ea"> <BODY BGCOLOR="#f6f2ea">
$settings_nav_bar $settings_nav_bar
<TABLE BORDER=0 CELLSPACING=0 WIDTH=100%> @{[ settings_message_html() ]}<TABLE BORDER=0 CELLSPACING=0 WIDTH=100%>
<TR BGCOLOR="#d7c0a0"> <TR BGCOLOR="#d7c0a0">
<TD> <TD>
<B><FONT SIZE=+2>Users</FONT></B> <B><FONT SIZE=+2>Users</FONT></B>
@@ -2553,6 +3057,9 @@ EOF
} }
} }
my $user_group_checkbox_rows = user_group_checkbox_rows( $c[2] );
my $current_unix_user = defined( $p{unix_user} ) ? $p{unix_user} : '';
my $user_unix_mapping_row = user_unix_mapping_row( $c[2], $current_unix_user );
print <<EOF; print <<EOF;
HTTP/1.0 200 OK HTTP/1.0 200 OK
Content-Type: text/html Content-Type: text/html
@@ -2586,11 +3093,13 @@ INPUT[type=SUBMIT], INPUT[type=RESET], INPUT[type=BUTTON] {
} }
A { color:#9f2f26; } A { color:#9f2f26; }
TT { color:#5b4b38; } TT { color:#5b4b38; }
.smart-message { margin:0 0 12px 0; padding:10px 12px; border:1px solid #b8d9a8; border-radius:12px; background:#edf7e8; color:#2f5b24; font-weight:bold; }
</style> </style>
</HEAD> </HEAD>
<BODY BGCOLOR="#f6f2ea"> <BODY BGCOLOR="#f6f2ea">
$settings_nav_bar $settings_nav_bar
@{[ settings_message_html() ]}
<FORM ACTION="/apply/users/$c[2]" METHOD=GET> <FORM ACTION="/apply/users/$c[2]" METHOD=GET>
<INPUT TYPE=HIDDEN NAME="unix_user_import_default" VALUE="$default_unix_user"> <INPUT TYPE=HIDDEN NAME="unix_user_import_default" VALUE="$default_unix_user">
<TABLE BORDER=0 CELLSPACING=0 WIDTH=100%> <TABLE BORDER=0 CELLSPACING=0 WIDTH=100%>
@@ -2635,21 +3144,14 @@ EOF
<INPUT NAME="password" TYPE=PASSWORD SIZE=20><BR> <INPUT NAME="password" TYPE=PASSWORD SIZE=20><BR>
</TD> </TD>
</TR> </TR>
<TR BGCOLOR="#fbf7f1"> $user_unix_mapping_row
<TD> <TR BGCOLOR="#fbf7f1">
<B>UNIX user:</B>
</TD>
<TD ALIGN=RIGHT>
<SELECT NAME="unix_user" SIZE=5>
$unix_user_list </SELECT>
</TD>
</TR>
<TR BGCOLOR="#fbf7f1">
<TD> <TD>
<B>Groups belonged to:</B> <B>Groups belonged to:</B>
</TD> </TD>
<TD ALIGN=RIGHT> <TD ALIGN=RIGHT>
$group_list </TD> $user_group_checkbox_rows
</TD>
</TR> </TR>
<TR BGCOLOR="#d7c0a0"> <TR BGCOLOR="#d7c0a0">
<TD> <TD>
@@ -2685,6 +3187,7 @@ EOF
} }
elsif( $c[1] eq 'groups' ) elsif( $c[1] eq 'groups' )
{ {
my $groups_service_guard_rows = mars_nwe_service_guard_rows( 'groups' );
$server = get_server(); $server = get_server();
if( $c[2] eq '' ) if( $c[2] eq '' )
@@ -2722,12 +3225,13 @@ INPUT[type=SUBMIT], INPUT[type=RESET], INPUT[type=BUTTON] {
} }
A { color:#9f2f26; } A { color:#9f2f26; }
TT { color:#5b4b38; } TT { color:#5b4b38; }
.smart-message { margin:0 0 12px 0; padding:10px 12px; border:1px solid #b8d9a8; border-radius:12px; background:#edf7e8; color:#2f5b24; font-weight:bold; }
</style> </style>
</HEAD> </HEAD>
<BODY BGCOLOR="#f6f2ea"> <BODY BGCOLOR="#f6f2ea">
$settings_nav_bar $settings_nav_bar
<TABLE BORDER=0 CELLSPACING=0 WIDTH=100%> @{[ settings_message_html() ]}<TABLE BORDER=0 CELLSPACING=0 WIDTH=100%>
<TR BGCOLOR="#d7c0a0"> <TR BGCOLOR="#d7c0a0">
<TD> <TD>
<B><FONT SIZE=+2>Groups</FONT></B> <B><FONT SIZE=+2>Groups</FONT></B>
@@ -2830,12 +3334,13 @@ INPUT[type=SUBMIT], INPUT[type=RESET], INPUT[type=BUTTON] {
} }
A { color:#9f2f26; } A { color:#9f2f26; }
TT { color:#5b4b38; } TT { color:#5b4b38; }
.smart-message { margin:0 0 12px 0; padding:10px 12px; border:1px solid #b8d9a8; border-radius:12px; background:#edf7e8; color:#2f5b24; font-weight:bold; }
</style> </style>
</HEAD> </HEAD>
<BODY BGCOLOR="#f6f2ea"> <BODY BGCOLOR="#f6f2ea">
$settings_nav_bar $settings_nav_bar
<FORM ACTION="/apply/groups/$c[2]" METHOD=GET> @{[ settings_message_html() ]}<FORM ACTION="/apply/groups/$c[2]" METHOD=GET>
<TABLE BORDER=0 CELLSPACING=0 WIDTH=100%> <TABLE BORDER=0 CELLSPACING=0 WIDTH=100%>
<TR BGCOLOR="#d7c0a0"> <TR BGCOLOR="#d7c0a0">
<TD> <TD>
@@ -2895,6 +3400,7 @@ EOF
} }
elsif( $c[1] eq 'queues' ) elsif( $c[1] eq 'queues' )
{ {
my $queues_service_guard_rows = mars_nwe_service_guard_rows( 'print queues' );
$server = get_server(); $server = get_server();
if( $c[2] eq '' ) if( $c[2] eq '' )
@@ -2932,12 +3438,13 @@ INPUT[type=SUBMIT], INPUT[type=RESET], INPUT[type=BUTTON] {
} }
A { color:#9f2f26; } A { color:#9f2f26; }
TT { color:#5b4b38; } TT { color:#5b4b38; }
.smart-message { margin:0 0 12px 0; padding:10px 12px; border:1px solid #b8d9a8; border-radius:12px; background:#edf7e8; color:#2f5b24; font-weight:bold; }
</style> </style>
</HEAD> </HEAD>
<BODY BGCOLOR="#f6f2ea"> <BODY BGCOLOR="#f6f2ea">
$settings_nav_bar $settings_nav_bar
<TABLE BORDER=0 CELLSPACING=0 WIDTH=100%> @{[ settings_message_html() ]}<TABLE BORDER=0 CELLSPACING=0 WIDTH=100%>
<TR BGCOLOR="#d7c0a0"> <TR BGCOLOR="#d7c0a0">
<TD> <TD>
<B><FONT SIZE=+2>Print queues</FONT></B> <B><FONT SIZE=+2>Print queues</FONT></B>
@@ -2947,17 +3454,37 @@ $settings_nav_bar
</TD> </TD>
</TR> </TR>
EOF EOF
my %queue_seen = ();
my @queue_names = ();
@c = sort( split( "\n", `nwbols -t 3 -S $server` ) ); @c = sort( split( "\n", `nwbols -t 3 -S $server` ) );
foreach $c ( @c ) foreach $c ( @c )
{ {
my @c = split( ' ', $c ); my @c = split( ' ', $c );
next if ! defined( $c[0] ) || $c[0] eq '';
next if $queue_seen{uc( $c[0] )};
push( @queue_names, $c[0] );
$queue_seen{uc( $c[0] )} = 1;
}
foreach my $q ( queue_names_from_config() )
{
next if $queue_seen{uc( $q )};
push( @queue_names, $q );
$queue_seen{uc( $q )} = 1;
}
foreach my $q ( sort( @queue_names ) )
{
print <<EOF; print <<EOF;
<TR BGCOLOR="#fbf7f1"> <TR BGCOLOR="#fbf7f1">
<TD> <TD>
<A HREF="/settings/queues/$c[0]">$c[0]</A> <A HREF="/settings/queues/$q">$q</A>
</TD> </TD>
<TD ALIGN=RIGHT> <TD ALIGN=RIGHT>
<A HREF="/apply/queues/$c[0]" onclick="return confirm('Delete queue $c[0]?')">Delete</A><BR> <A HREF="/apply/queues/$q?test_print=1">Test print</A>
&nbsp;|&nbsp;
<A HREF="/apply/queues/$q" onclick="return confirm('Delete queue $q?')">Delete</A><BR>
</TD> </TD>
</TR> </TR>
EOF EOF
@@ -2986,6 +3513,10 @@ EOF
{ {
$unix_print = read_property_string( $c[2], 3, 'Q_UNIX_PRINT' ); $unix_print = read_property_string( $c[2], 3, 'Q_UNIX_PRINT' );
$spool_dir = read_property_string( $c[2], 3, 'Q_DIRECTORY' ); $spool_dir = read_property_string( $c[2], 3, 'Q_DIRECTORY' );
my( $cfg_spool, $cfg_cmd ) = queue_config_by_name( $c[2] );
$spool_dir = $cfg_spool if ( ! defined( $spool_dir ) || $spool_dir eq '' ) && $cfg_spool ne '';
$unix_print = $cfg_cmd if ( ! defined( $unix_print ) || $unix_print eq '' ) && $cfg_cmd ne '';
} }
elsif( $selected_cups_printer ne '' ) elsif( $selected_cups_printer ne '' )
{ {
@@ -3028,6 +3559,7 @@ INPUT[type=SUBMIT], INPUT[type=RESET], INPUT[type=BUTTON] {
} }
A { color:#9f2f26; } A { color:#9f2f26; }
TT { color:#5b4b38; } TT { color:#5b4b38; }
.smart-message { margin:0 0 12px 0; padding:10px 12px; border:1px solid #b8d9a8; border-radius:12px; background:#edf7e8; color:#2f5b24; font-weight:bold; }
</style> </style>
<SCRIPT TYPE="text/javascript"> <SCRIPT TYPE="text/javascript">
var smartCupsCommands = {}; var smartCupsCommands = {};
@@ -3053,7 +3585,7 @@ window.onload = function() {
<BODY BGCOLOR="#f6f2ea"> <BODY BGCOLOR="#f6f2ea">
$settings_nav_bar $settings_nav_bar
<FORM ACTION="/apply/queues/$c[2]" METHOD=GET> @{[ settings_message_html() ]}<FORM ACTION="/apply/queues/$c[2]" METHOD=GET>
<TABLE BORDER=0 CELLSPACING=0 WIDTH=100%> <TABLE BORDER=0 CELLSPACING=0 WIDTH=100%>
<TR BGCOLOR="#d7c0a0"> <TR BGCOLOR="#d7c0a0">
<TD> <TD>
@@ -3113,6 +3645,22 @@ EOF
<INPUT NAME="spool_dir" TYPE=TEXT SIZE=32 VALUE="$spool_dir"><BR> <INPUT NAME="spool_dir" TYPE=TEXT SIZE=32 VALUE="$spool_dir"><BR>
</TD> </TD>
</TR> </TR>
EOF
if( $c[2] ne 'add_new' )
{
print <<EOF;
<TR BGCOLOR="#fbf7f1">
<TD>
<B>Queue test:</B><BR>
<SMALL>Submit a small test job with nprint.</SMALL>
</TD>
<TD ALIGN=RIGHT>
<A HREF="/apply/queues/$c[2]?test_print=1">Test print queue</A><BR>
</TD>
</TR>
EOF
}
print <<EOF;
<TR BGCOLOR="#d7c0a0"> <TR BGCOLOR="#d7c0a0">
<TD> <TD>
<INPUT TYPE=SUBMIT VALUE="OK"> <INPUT TYPE=SUBMIT VALUE="OK">
@@ -3202,12 +3750,13 @@ INPUT[type=SUBMIT], INPUT[type=RESET], INPUT[type=BUTTON] { border:1px solid #a3
A { color:#9f2f26; } A { color:#9f2f26; }
TT { color:#5b4b38; } TT { color:#5b4b38; }
.smallnote { color:#6f6257; font-size:12px; } .smallnote { color:#6f6257; font-size:12px; }
.smart-message { margin:0 0 12px 0; padding:10px 12px; border:1px solid #b8d9a8; border-radius:12px; background:#edf7e8; color:#2f5b24; font-weight:bold; }
</style> </style>
</HEAD> </HEAD>
<BODY BGCOLOR="#f6f2ea"> <BODY BGCOLOR="#f6f2ea">
$settings_nav_bar $settings_nav_bar
<FORM ACTION="/apply/advanced" METHOD=GET> @{[ settings_message_html() ]}<FORM ACTION="/apply/advanced" METHOD=GET>
$advanced_hidden $advanced_hidden
<TABLE BORDER=0 CELLSPACING=0 WIDTH=100%> <TABLE BORDER=0 CELLSPACING=0 WIDTH=100%>
<TR BGCOLOR="#d7c0a0"> <TR BGCOLOR="#d7c0a0">

View File

@@ -31,7 +31,21 @@ do( '@MARS_NWE_INSTALL_FULL_CONFDIR@/smart.conf' )
or die "Could not load @MARS_NWE_INSTALL_FULL_CONFDIR@/smart.conf: $@ $!"; or die "Could not load @MARS_NWE_INSTALL_FULL_CONFDIR@/smart.conf: $@ $!";
close( STDERR ); close( STDERR );
open( STDERR, '>>' . $smart_log_path )
# 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: $!"; or die "Could not open $smart_log_path: $!";
$ENV{HOME} = '@MARS_NWE_INSTALL_FULL_CONFDIR@'; $ENV{HOME} = '@MARS_NWE_INSTALL_FULL_CONFDIR@';
@@ -41,6 +55,8 @@ $smart_libexec_dir =~ s#/*$##;
$smart_control_path = $smart_libexec_dir . '/control' unless defined $smart_control_path; $smart_control_path = $smart_libexec_dir . '/control' unless defined $smart_control_path;
$mars_nwe_service = '@MARS_NWE_SYSTEMD_SERVICE@' unless defined $mars_nwe_service; $mars_nwe_service = '@MARS_NWE_SYSTEMD_SERVICE@' unless defined $mars_nwe_service;
$smart_systemctl_path = '@SYSTEMCTL_EXECUTABLE@' unless defined $smart_systemctl_path; $smart_systemctl_path = '@SYSTEMCTL_EXECUTABLE@' unless defined $smart_systemctl_path;
$smart_admin_group = '@MARS_NWE_SMART_ADMIN_GROUP@' unless defined $smart_admin_group;
$smart_admin_group = 'root' if ! defined( $smart_admin_group ) || $smart_admin_group eq '' || $smart_admin_group =~ /^\@MARS_NWE_SMART_ADMIN_GROUP\@$/;
$l = <STDIN>; $l = <STDIN>;
$l =~ s/[\n\r]//g; $l =~ s/[\n\r]//g;
@@ -114,6 +130,15 @@ if( $c[0] eq 'logout' )
exit; exit;
} }
# Static assets must be available before login, otherwise the login page
# cannot load the SMArT logo and icons.
if( $c[0] eq 'static' )
{
do( $smart_libexec_dir . '/static.pl' );
handle_request();
exit;
}
if( ! valid_session() ) if( ! valid_session() )
{ {
redirect( '/login' ); redirect( '/login' );
@@ -414,14 +439,28 @@ sub check_login_password( $$ )
my( $user, $pass ) = @_; my( $user, $pass ) = @_;
return 0 if ! defined( $user ) || ! defined( $pass ); return 0 if ! defined( $user ) || ! defined( $pass );
return 0 if $user ne 'root'; return 0 if $user eq '' || $pass eq '';
if( ! defined( $smart_check_login ) || $smart_check_login eq '' || ! -x $smart_check_login ) if( ! defined( $smart_check_login ) || $smart_check_login eq '' || ! -x $smart_check_login )
{ {
return -1; return -1;
} }
return system( $smart_check_login, $user, $pass ) == 0 ? 1 : 0; my $admin_group = defined( $smart_admin_group ) && $smart_admin_group ne '' ? $smart_admin_group : 'root';
my $conf_path = defined( $smart_conf_path ) && $smart_conf_path ne '' ? $smart_conf_path : '@MARS_NWE_INSTALL_FULL_CONFDIR@/smart.conf';
my $rc = system( $smart_check_login, $user, $pass, $admin_group, $conf_path );
if( $rc == 0 )
{
return 1;
}
my $exit = $rc >> 8;
return -2 if $exit == 2;
return 0;
} }
sub print_login_page( $ ) sub print_login_page( $ )
@@ -445,20 +484,20 @@ html,body{margin:0;padding:0;min-height:100%;background:var(--bg);color:var(--te
body{display:flex;align-items:center;justify-content:center;padding:24px} 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} .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{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{display:flex;align-items:center;gap:18px}.hero img{width:120px;max-width:34%;height:auto;display:block;background:#fff;border-radius:16px;padding:8px 10px;box-shadow:0 8px 20px rgba(0,0,0,.12)}.hero h1{margin:0;font-size:28px}
.hero p{margin:6px 0 0;opacity:.95} .hero p{margin:6px 0 0;opacity:.95}
form{padding:24px 28px 28px} form{padding:24px 28px 28px}
label{display:block;font-weight:bold;margin:0 0 7px} 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} 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} 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} .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} .note{margin-top:14px;color:var(--muted);font-size:13px;text-align:center;line-height:1.45}
</style> </style>
</head> </head>
<body> <body>
<div class="login"> <div class="login">
<div class="hero"> <div class="hero">
<img src="/static/smart_icon.jpg" alt="SMArT logo"> <img src="/static/smart.jpg" alt="SMArT logo">
<div> <div>
<h1>SMArT Login</h1> <h1>SMArT Login</h1>
<p>MARS_NWE web administration</p> <p>MARS_NWE web administration</p>
@@ -478,6 +517,7 @@ EOF
<label for="pass">Password</label> <label for="pass">Password</label>
<input id="pass" name="pass" type="password" autocomplete="current-password" autofocus> <input id="pass" name="pass" type="password" autocomplete="current-password" autofocus>
<button type="submit">Login</button> <button type="submit">Login</button>
<div class="note">&copy; Copyright 2026 Mario Fetka</div>
</form> </form>
</div> </div>
</body> </body>
@@ -501,6 +541,13 @@ sub handle_login_route()
return; return;
} }
if( $rv == -2 )
{
my $admin_group = defined( $smart_admin_group ) && $smart_admin_group ne '' ? $smart_admin_group : 'root';
print_login_page( 'Login denied. User is not a member of required admin group: ' . $admin_group );
return;
}
if( $rv != 1 ) if( $rv != 1 )
{ {
print_login_page( 'Login failed.' ); print_login_page( 'Login failed.' );

View File

@@ -61,9 +61,32 @@ $smart_static_dir = '@MARS_NWE_INSTALL_FULL_LIBEXECDIR@/static';
# Keep this separate from the nwwebui log file. # Keep this separate from the nwwebui log file.
$smart_log_path = '@MARS_NWE_LOG_DIR@/smart.log'; $smart_log_path = '@MARS_NWE_LOG_DIR@/smart.log';
# Path to the PAM-based login helper used for root authentication. # Path to the PAM-based login helper used for SMArT authentication.
$smart_check_login = '@MARS_NWE_INSTALL_FULL_LIBEXECDIR@/check_login'; $smart_check_login = '@MARS_NWE_INSTALL_FULL_LIBEXECDIR@/check_login';
# Path to the native Unix-user enumeration helper used by the user editor.
$smart_userlist_path = '@MARS_NWE_INSTALL_FULL_LIBEXECDIR@/smart_userlist';
# Unix group allowed to log in to the SMArT/nwwebui admin interface.
#
# Authentication is still done through PAM service "smart", but a user must
# also be a member of this Unix group.
#
# The build-time default is "root" to preserve the traditional behavior on
# existing installations: the root user is allowed because its primary Unix
# group is normally also "root". Do not add normal users to the "root" group.
#
# For delegated administration, use a dedicated group instead, for example:
#
# cmake -DMARS_NWE_SMART_ADMIN_GROUP=nwadmin ...
# groupadd nwadmin
# usermod -aG nwadmin mario
#
# Changes to local group membership normally require the user to start a new
# login session before NSS/PAM reports the new membership.
$smart_admin_group = '@MARS_NWE_SMART_ADMIN_GROUP@';
# Path to the SMArT service-control helper. # Path to the SMArT service-control helper.
$smart_control_path = '@MARS_NWE_INSTALL_FULL_LIBEXECDIR@/control'; $smart_control_path = '@MARS_NWE_INSTALL_FULL_LIBEXECDIR@/control';
@@ -105,11 +128,19 @@ $smart_cups_print_command_template = '@CUPS_LP_EXECUTABLE@ -d %p -';
# Use 127.0.0.1 for local-only testing. # Use 127.0.0.1 for local-only testing.
$nw_bind_ip = '0.0.0.0'; $nw_bind_ip = '0.0.0.0';
# Log level used by nwwebui. # Log level used by the native nwwebui frontend service.
# 0 = errors only #
# 1 = informational messages # Supported values, from quiet to verbose:
# 2 = debug messages #
$nw_log_level = 1; # error - only real errors
# warning - errors and warnings
# info - normal operational messages, default
# debug - additional diagnostic information
# trace - very verbose request/connection tracing
#
# Older numeric values are still accepted for compatibility, but named values
# are preferred for new configurations.
$nw_log_level = 'info';
# Run nwwebui in daemon mode by default. # Run nwwebui in daemon mode by default.
# 0 = stay in foreground # 0 = stay in foreground
@@ -150,3 +181,33 @@ $nw_key_file = '@MARS_NWE_INSTALL_FULL_CONFDIR@/server.key';
# Directory for HTML login cookie sessions. Created by systemd RuntimeDirectory. # Directory for HTML login cookie sessions. Created by systemd RuntimeDirectory.
$smart_session_dir = '/run/mars-nwe-webui'; $smart_session_dir = '/run/mars-nwe-webui';
$smart_session_timeout = 3600; $smart_session_timeout = 3600;
# SMArT Perl logging verbosity.
#
# This controls log messages written by the Perl CGI-style helper scripts
# such as apply.pl. The messages are written to the SMArT log file configured
# for the WebUI, normally:
#
# /var/log/mars_nwe/smart.log
#
# Supported values, from quiet to verbose:
#
# error - only real errors that abort or fail an operation
# warning - errors and warnings about unusual but non-fatal situations
# info - normal operational messages, command start/finish, default
# debug - additional diagnostic information for troubleshooting
# trace - very verbose step-by-step traces, including bindery pipe payloads
#
# Recommended setting for normal operation:
#
# $smart_debug_level = 'info';
#
# Use 'trace' only while debugging a concrete problem. Trace logging may
# include submitted bindery payload data and can produce a lot of log output.
# After debugging, switch back to 'info'.
$smart_debug_level = 'info';
# ncpfs nprint executable used by the queue test action.
$smart_nprint_path = '/usr/bin/nprint';

View File

@@ -3,24 +3,262 @@
List local/NSS users for the WebUI. List local/NSS users for the WebUI.
PAM itself cannot enumerate users. User enumeration is done through NSS Usage:
getpwent(), so /etc/nsswitch.conf is honored (files, sss, ldap, nis, ...). smart_userlist [--config /etc/mars_nwe/smart.conf] [--all]
Optionally, each user can be checked with pam_acct_mgmt() against the "smart" [--min-uid UID] [--pam-check] [--pam-service SERVICE]
PAM service.
Output format: Output format on stdout stays unchanged:
username<TAB>uid<TAB>gid<TAB>gecos<TAB>home<TAB>shell username<TAB>uid<TAB>gid<TAB>gecos<TAB>home<TAB>shell
*/ */
#include <ctype.h>
#include <errno.h> #include <errno.h>
#include <pwd.h> #include <pwd.h>
#include <security/pam_appl.h> #include <security/pam_appl.h>
#include <stdarg.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <sys/types.h> #include <sys/types.h>
#include <time.h>
#include <unistd.h> #include <unistd.h>
#include "config.h"
#define SMART_LOG_ERROR 0
#define SMART_LOG_WARNING 1
#define SMART_LOG_INFO 2
#define SMART_LOG_DEBUG 3
#define SMART_LOG_TRACE 4
typedef struct {
char log_path[512];
char debug_level[64];
char admin_group[256];
int level;
} smart_helper_config_t;
static void trim(char *s)
{
char *p = s;
size_t len;
while (*p && isspace((unsigned char)*p)) {
p++;
}
if (p != s) {
memmove(s, p, strlen(p) + 1);
}
len = strlen(s);
while (len > 0 && isspace((unsigned char)s[len - 1])) {
s[len - 1] = '\0';
len--;
}
}
static void strip_quotes(char *s)
{
size_t len = strlen(s);
if (len >= 2) {
if ((s[0] == '\'' && s[len - 1] == '\'') ||
(s[0] == '"' && s[len - 1] == '"')) {
memmove(s, s + 1, len - 2);
s[len - 2] = '\0';
}
}
}
static int parse_perl_assignment(const char *line, char *key, size_t ksz, char *val, size_t vsz)
{
const char *p = line;
size_t ki = 0;
size_t vi = 0;
while (*p && isspace((unsigned char)*p)) {
p++;
}
if (*p != '$') {
return 0;
}
p++;
while (*p && (isalnum((unsigned char)*p) || *p == '_')) {
if (ki + 1 < ksz) {
key[ki++] = *p;
}
p++;
}
key[ki] = '\0';
while (*p && isspace((unsigned char)*p)) {
p++;
}
if (*p != '=') {
return 0;
}
p++;
while (*p && isspace((unsigned char)*p)) {
p++;
}
while (*p && *p != ';' && *p != '\n' && *p != '\r') {
if (vi + 1 < vsz) {
val[vi++] = *p;
}
p++;
}
val[vi] = '\0';
trim(key);
trim(val);
strip_quotes(val);
return key[0] != '\0';
}
static int parse_log_level(const char *value)
{
char buf[64];
size_t i;
if (value == NULL || value[0] == '\0') {
return SMART_LOG_INFO;
}
snprintf(buf, sizeof(buf), "%s", value);
trim(buf);
for (i = 0; buf[i]; i++) {
buf[i] = (char)tolower((unsigned char)buf[i]);
}
if (strcmp(buf, "error") == 0 || strcmp(buf, "err") == 0 || strcmp(buf, "0") == 0) {
return SMART_LOG_ERROR;
}
if (strcmp(buf, "warning") == 0 || strcmp(buf, "warn") == 0 || strcmp(buf, "1") == 0) {
return SMART_LOG_WARNING;
}
if (strcmp(buf, "info") == 0 || strcmp(buf, "2") == 0) {
return SMART_LOG_INFO;
}
if (strcmp(buf, "debug") == 0 || strcmp(buf, "3") == 0) {
return SMART_LOG_DEBUG;
}
if (strcmp(buf, "trace") == 0 || strcmp(buf, "4") == 0) {
return SMART_LOG_TRACE;
}
return SMART_LOG_INFO;
}
static const char *level_name(int level)
{
if (level <= SMART_LOG_ERROR) {
return "ERROR";
}
if (level == SMART_LOG_WARNING) {
return "WARNING";
}
if (level == SMART_LOG_DEBUG) {
return "DEBUG";
}
if (level >= SMART_LOG_TRACE) {
return "TRACE";
}
return "INFO";
}
static void smart_cfg_init(smart_helper_config_t *cfg)
{
memset(cfg, 0, sizeof(*cfg));
snprintf(cfg->log_path, sizeof(cfg->log_path), "%s", DEFAULT_SMART_LOG_PATH);
snprintf(cfg->debug_level, sizeof(cfg->debug_level), "%s", DEFAULT_SMART_LOG_LEVEL);
snprintf(cfg->admin_group, sizeof(cfg->admin_group), "%s", "root");
cfg->level = parse_log_level(cfg->debug_level);
}
static void smart_cfg_load(smart_helper_config_t *cfg, const char *path)
{
FILE *fh;
char line[2048];
if (path == NULL || path[0] == '\0') {
return;
}
fh = fopen(path, "r");
if (fh == NULL) {
return;
}
while (fgets(line, sizeof(line), fh) != NULL) {
char key[256];
char val[1024];
if (!parse_perl_assignment(line, key, sizeof(key), val, sizeof(val))) {
continue;
}
if (strcmp(key, "smart_log_path") == 0) {
snprintf(cfg->log_path, sizeof(cfg->log_path), "%s", val);
} else if (strcmp(key, "smart_debug_level") == 0 ||
strcmp(key, "smart_log_level") == 0) {
snprintf(cfg->debug_level, sizeof(cfg->debug_level), "%s", val);
cfg->level = parse_log_level(val);
} else if (strcmp(key, "smart_admin_group") == 0) {
snprintf(cfg->admin_group, sizeof(cfg->admin_group), "%s", val);
}
}
fclose(fh);
}
static void helper_log(smart_helper_config_t *cfg, const char *component, int level, const char *fmt, ...)
{
FILE *fh = stderr;
int close_fh = 0;
time_t now;
struct tm tm_now;
char tbuf[64];
va_list ap;
if (cfg != NULL && level > cfg->level) {
return;
}
if (cfg != NULL && cfg->log_path[0] != '\0') {
fh = fopen(cfg->log_path, "a");
if (fh != NULL) {
close_fh = 1;
} else {
fh = stderr;
}
}
now = time(NULL);
localtime_r(&now, &tm_now);
strftime(tbuf, sizeof(tbuf), "%Y-%m-%d %H:%M:%S", &tm_now);
fprintf(fh, "[%s] [%s] [SMArT helper] [%s] ", tbuf, level_name(level), component);
va_start(ap, fmt);
vfprintf(fh, fmt, ap);
va_end(ap);
fputc('\n', fh);
fflush(fh);
if (close_fh) {
fclose(fh);
}
}
static int empty_conv(int num_msg, const struct pam_message **msg, static int empty_conv(int num_msg, const struct pam_message **msg,
struct pam_response **resp, void *appdata_ptr) struct pam_response **resp, void *appdata_ptr)
{ {
@@ -103,7 +341,13 @@ int main(int argc, char **argv)
int include_system = 0; int include_system = 0;
int pam_check = 0; int pam_check = 0;
const char *pam_service = "smart"; const char *pam_service = "smart";
const char *smart_conf = DEFAULT_SMART_CONF;
int i; int i;
unsigned long emitted = 0;
unsigned long skipped = 0;
smart_helper_config_t cfg;
smart_cfg_init(&cfg);
for (i = 1; i < argc; i++) { for (i = 1; i < argc; i++) {
if (strcmp(argv[i], "--all") == 0) { if (strcmp(argv[i], "--all") == 0) {
@@ -113,6 +357,8 @@ int main(int argc, char **argv)
char *end = NULL; char *end = NULL;
unsigned long v = strtoul(argv[++i], &end, 10); unsigned long v = strtoul(argv[++i], &end, 10);
if (end == NULL || *end != '\0') { if (end == NULL || *end != '\0') {
smart_cfg_load(&cfg, smart_conf);
helper_log(&cfg, "smart_userlist", SMART_LOG_ERROR, "invalid --min-uid value");
fprintf(stderr, "Invalid --min-uid value\n"); fprintf(stderr, "Invalid --min-uid value\n");
return 2; return 2;
} }
@@ -121,32 +367,46 @@ int main(int argc, char **argv)
pam_check = 1; pam_check = 1;
} else if (strcmp(argv[i], "--pam-service") == 0 && i + 1 < argc) { } else if (strcmp(argv[i], "--pam-service") == 0 && i + 1 < argc) {
pam_service = argv[++i]; pam_service = argv[++i];
} else if (strcmp(argv[i], "--config") == 0 && i + 1 < argc) {
smart_conf = argv[++i];
} else { } else {
smart_cfg_load(&cfg, smart_conf);
helper_log(&cfg, "smart_userlist", SMART_LOG_ERROR, "invalid command line");
fprintf(stderr, fprintf(stderr,
"Usage: %s [--all] [--min-uid UID] [--pam-check] [--pam-service SERVICE]\n", "Usage: %s [--config FILE] [--all] [--min-uid UID] [--pam-check] [--pam-service SERVICE]\n",
argv[0]); argv[0]);
return 2; return 2;
} }
} }
smart_cfg_load(&cfg, smart_conf);
helper_log(&cfg, "smart_userlist", SMART_LOG_DEBUG,
"user enumeration started include_system=%d min_uid=%lu pam_check=%d pam_service='%s'",
include_system, (unsigned long) min_uid, pam_check, pam_service);
errno = 0; errno = 0;
setpwent(); setpwent();
while ((pw = getpwent()) != NULL) { while ((pw = getpwent()) != NULL) {
if (!is_safe_name(pw->pw_name)) { if (!is_safe_name(pw->pw_name)) {
skipped++;
continue; continue;
} }
if (!include_system && pw->pw_uid < min_uid) { if (!include_system && pw->pw_uid < min_uid) {
skipped++;
continue; continue;
} }
if (!include_system && if (!include_system &&
(strcmp(pw->pw_name, "root") == 0 || strcmp(pw->pw_name, "nobody") == 0)) { (strcmp(pw->pw_name, "root") == 0 || strcmp(pw->pw_name, "nobody") == 0)) {
skipped++;
continue; continue;
} }
if (pam_check && !pam_account_ok(pam_service, pw->pw_name)) { if (pam_check && !pam_account_ok(pam_service, pw->pw_name)) {
skipped++;
continue; continue;
} }
@@ -158,14 +418,21 @@ int main(int argc, char **argv)
putchar('\t'); putchar('\t');
print_sanitized(pw->pw_shell); print_sanitized(pw->pw_shell);
putchar('\n'); putchar('\n');
emitted++;
} }
endpwent(); endpwent();
if (errno != 0) { if (errno != 0) {
helper_log(&cfg, "smart_userlist", SMART_LOG_ERROR, "getpwent failed: %s", strerror(errno));
perror("getpwent"); perror("getpwent");
return 1; return 1;
} }
helper_log(&cfg, "smart_userlist", SMART_LOG_DEBUG,
"user enumeration finished emitted=%lu skipped=%lu",
emitted, skipped);
return 0; return 0;
} }

View File

@@ -90,11 +90,11 @@ a{color:inherit} code,tt{font-family:"DejaVu Sans Mono",monospace}
<aside class="nav-shell"> <aside class="nav-shell">
<div class="nav-title">Sections</div> <div class="nav-title">Sections</div>
<div class="nav-list"> <div class="nav-list">
<button class="nav-item active" type="button" data-target="setup-first" data-href="/settings/smart" aria-pressed="true" title="Setup first"> <button class="nav-item active" type="button" data-target="setup-first" data-href="/settings/smart" aria-pressed="false" title="Setup first">
<span class="nav-icon icon-start"><img src="/static/icon-start.svg" alt="" aria-hidden="true"></span> <span class="nav-icon icon-start"><img src="/static/icon-start.svg" alt="" aria-hidden="true"></span>
<span class="nav-label">Setup first</span> <span class="nav-label">Setup first</span>
</button> </button>
<button class="nav-item" type="button" data-target="mars-nwe-service" data-href="/static/start.html" aria-pressed="false" title="MARS_NWE service"> <button class="nav-item active" type="button" data-target="mars-nwe-service" data-href="/static/start.html" aria-pressed="true" title="MARS_NWE service">
<span class="nav-icon icon-service"><img src="/static/icon-service.svg" alt="" aria-hidden="true"></span> <span class="nav-icon icon-service"><img src="/static/icon-service.svg" alt="" aria-hidden="true"></span>
<span class="nav-label">MARS_NWE service</span> <span class="nav-label">MARS_NWE service</span>
</button> </button>
@@ -626,5 +626,26 @@ default directory.
} }
})(); })();
</script> </script>
<script>
(function smartDefaultSection(){
function openDefault(){
if (window.location.hash) return;
var btn = document.querySelector('[data-target="mars-nwe-service"]');
if (btn && typeof btn.click === 'function') {
btn.click();
return;
}
var frame = parent && parent.frames ? parent.frames['OPTS'] : null;
if (frame) frame.location = '/static/start.html';
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', openDefault);
} else {
openDefault();
}
})();
</script>
</body> </body>
</html> </html>

View File

@@ -73,7 +73,11 @@
font-size: 13px; font-size: 13px;
color: #6c5b52; color: #6c5b52;
} }
</STYLE>
.runtime-info{margin:18px 12px 0;border:1px solid #ddcfba;border-radius:16px;overflow:hidden;background:#fbf7f1;box-shadow:0 6px 18px rgba(80,55,30,.06)}.runtime-info-title{background:#d7c0a0;padding:10px 14px;font-weight:bold;color:#2e261d}.runtime-info-row{display:grid;grid-template-columns:190px minmax(0,1fr);gap:12px;padding:8px 14px;border-top:1px solid #eadfce;align-items:center}.runtime-info-row code{color:#5b4b38;white-space:normal;word-break:break-word}.runtime-info-note{padding:10px 14px;border-top:1px solid #eadfce;color:#6b5b50;font-size:13px;line-height:1.35}
.project-footer{margin:16px auto 0;text-align:center;color:#6b5b50;font-size:14px;line-height:1.45;max-width:760px}.project-footer a{color:#9f2f26;text-decoration:none}.project-footer a:hover{text-decoration:underline}.copyright{margin:10px auto 0;text-align:center;color:#6b5b50;font-size:13px;max-width:760px}.copyright a{color:#9f2f26;text-decoration:none}.copyright a:hover{text-decoration:underline}
</STYLE>
</HEAD> </HEAD>
<BODY> <BODY>
<DIV CLASS="wrapper"> <DIV CLASS="wrapper">
@@ -105,13 +109,42 @@
</TR> </TR>
<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?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> <TD WIDTH="50%"><A CLASS="action secondary" HREF="/service/control?status">Status <TT>MARS_NWE</TT></A>
</TD>
</TR> </TR>
</TABLE> </TABLE>
</DIV> </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> <DIV CLASS="runtime-info">
&copy; Copyright 2026 <A HREF="mailto:mario.fetka@disconnected-by-peer.at">Mario Fetka</A> <DIV CLASS="runtime-info-title">MARS_NWE runtime information</DIV>
<DIV CLASS="runtime-info-row">
<DIV>Configuration file</DIV>
<DIV><CODE>@MARS_NWE_INSTALL_FULL_CONFDIR@/nwserv.conf</CODE></DIV>
</DIV>
<DIV CLASS="runtime-info-row">
<DIV>SMArT configuration</DIV>
<DIV><CODE>@MARS_NWE_INSTALL_FULL_CONFDIR@/smart.conf</CODE></DIV>
</DIV>
<DIV CLASS="runtime-info-row">
<DIV>WebUI scripts</DIV>
<DIV><CODE>@MARS_NWE_INSTALL_FULL_LIBEXECDIR@</CODE></DIV>
</DIV>
<DIV CLASS="runtime-info-row">
<DIV>MARS_NWE service</DIV>
<DIV><CODE>mars-nwe-serv.service</CODE></DIV>
</DIV>
<DIV CLASS="runtime-info-note">
User, group and queue operations need a running <B>MARS_NWE</B> service and a reachable bindery.
Configuration changes require write access to the SMArT configuration directory.
</DIV>
</DIV>
<DIV CLASS="footer">
<DIV CLASS="project-footer">
<DIV>SMArT is shipped as part of the <B>MARS_NWE</B> package.</DIV>
<DIV>Project sources are available from the <A HREF="https://gitea.disconnected-by-peer.at/mars_nwe/mars-nwe" TARGET="_blank">MARS_NWE repository</A>.</DIV>
</DIV><BR><BR>
<DIV CLASS="copyright">&copy; Copyright 2026 <A HREF="mailto:mario.fetka@disconnected-by-peer.at">Mario Fetka</A></DIV>
</DIV> </DIV>
</DIV> </DIV>
</DIV> </DIV>