From acdd93166797821233fe59977b38ff48db6185c0 Mon Sep 17 00:00:00 2001
From: Daniel Marschall <info@daniel-marschall.de>
Date: Sat, 8 Apr 2023 14:06:10 +0000
Subject: [PATCH] File attachments plugin: If directory is not writeable or
 otherwise invalid, the admin will see a warning in the "notifcations" area

git-svn-id: https://svn.viathinksoft.com/svn/oidplus/trunk@1180 02e4d621-2042-4998-a0bc-9ccd24201011
---
 README.md                                     |  2 +-
 TODO                                          |  2 +-
 includes/functions.inc.php                    | 75 +++++++++++++++++++
 ..._OID_1_3_6_1_4_1_37476_2_5_2_3_8.class.php |  2 +-
 .../OIDplusPageAdminNotifications.class.php   |  9 ++-
 .../OIDplusPageAdminSysteminfo.class.php      | 46 +-----------
 .../OIDplusPageAdminSoftwareUpdate.class.php  |  2 +-
 .../viathinksoft/language/dede/messages.xml   | 16 ++++
 .../OIDplusPagePublicAttachments.class.php    | 46 +++++++++++-
 9 files changed, 146 insertions(+), 54 deletions(-)

diff --git a/README.md b/README.md
index e9c861c4..c3e8d716 100644
--- a/README.md
+++ b/README.md
@@ -24,7 +24,7 @@ https://www.oidplus.com/
 Download a TAR.GZ file here: https://www.viathinksoft.com/projects/oidplus
 
 ### System requirements
-- PHP compatible web server (tested with Apache 2, nginx and Microsoft IIS)
+- PHP compatible web server (tested with Apache 2, nginx, and Microsoft IIS)
 - PHP 7.0 or higher (tested till PHP version 8.2 inclusive)
         with extension MySQLi, PostgreSQL, SQLite3, PDO, OCI8, or ODBC, depending on your database
 - Supported databases:
diff --git a/TODO b/TODO
index 103fe121..0a5045cd 100644
--- a/TODO
+++ b/TODO
@@ -3,7 +3,7 @@ April 2023 planned:
 - Don't send information object OIDs to oid-info.com anymore
 - https://github.com/danielmarschall/oidplus/issues/5 => "Offline mode" (do not contact internet, e.g. gs1-barcodes, polyfill, oidinfo, ...)
 - Everywhere where url_post_contents() is used, we need to check url_post_contents_available() too
-- Attachments plugin: Via "notifications", let the admin know if the upload directory is writeable
+    Maybe also url_get_contents_available() which can either return bool or raise an Exception with the requirements (e.g. "CURL is missing" or "OIDplus is in offline mode")
 
 Type safety:
 - PhpStorm warnings
diff --git a/includes/functions.inc.php b/includes/functions.inc.php
index dbed65ac..07068954 100644
--- a/includes/functions.inc.php
+++ b/includes/functions.inc.php
@@ -479,3 +479,78 @@ function stdobj_to_array(\stdClass $obj): array {
 	}
 	return $ary;
 }
+
+/**
+ * @return string|false
+ */
+function get_own_username() {
+	$current_user = exec('whoami');
+	if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
+		try {
+			if (function_exists('mb_convert_encoding')) {
+				$current_user = @mb_convert_encoding($current_user, "UTF-8", "cp850");
+			} else if (function_exists('iconv')) {
+				$current_user = @iconv("cp850", "UTF-8", $current_user);
+			}
+		} catch (\Exception $e) {}
+		if (function_exists('mb_strtoupper')) {
+			$current_user = mb_strtoupper($current_user); // just cosmetics
+		}
+	}
+	if (!$current_user) {
+		if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
+			// Windows on an IIS server:
+			//     getenv('USERNAME')     MARSCHALL$                (That is the "machine account", see https://docs.microsoft.com/en-us/iis/manage/configuring-security/application-pool-identities#accessing-the-network )
+			//     get_current_user()     DefaultAppPool
+			//     exec('whoami')         iis apppool\defaultapppool
+			// Windows with XAMPP:
+			//     getenv('USERNAME')     dmarschall
+			//     get_current_user()     dmarschall               (even if script has a different NTFS owner!)
+			//     exec('whoami')         hickelsoft\dmarschall
+			$current_user = get_current_user();
+			if (!$current_user) {
+				$current_user = getenv('USERNAME');
+				$current_user = mb_strtoupper($current_user); // just cosmetics
+			}
+		} else {
+			// On Linux:
+			$current_user = exec('id -un');
+			if (!$current_user) {
+				// PHP'S get_current_user() will get the owner of the PHP script, not the process owner!
+				// We want the process owner, so we use posix_geteuid() preferably.
+				if (function_exists('posix_geteuid')) {
+					$uid = posix_geteuid();
+				} else {
+					$temp_file = tempnam(sys_get_temp_dir(), 'TMP');
+					if ($temp_file !== false) {
+						$uid = fileowner($temp_file);
+						if ($uid === false) $uid = -1;
+						@unlink($temp_file);
+					} else {
+						$uid = -1;
+					}
+				}
+				if ($uid >= 0) {
+					$current_user = '#' . $uid;
+					if (function_exists('posix_getpwuid')) {
+						$userinfo = posix_getpwuid($uid); // receive username from the UID (requires read access to /etc/passwd)
+						if ($userinfo !== false) $current_user = $userinfo['name'];
+					}
+				} else {
+					$current_user = get_current_user();
+				}
+			}
+		}
+	}
+	return $current_user ?: false;
+}
+
+/**
+ * @param string $path
+ * @return bool
+ */
+function isFileOrPathWritable(string $path): bool {
+	if ($writable_file = (file_exists($path) && is_writable($path))) return true;
+	if ($writable_directory = (!file_exists($path) && is_writable(dirname($path)))) return true;
+	return false;
+}
\ No newline at end of file
diff --git a/plugins/viathinksoft/adminPages/010_notifications/INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_8.class.php b/plugins/viathinksoft/adminPages/010_notifications/INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_8.class.php
index d3541a7b..fd77366b 100644
--- a/plugins/viathinksoft/adminPages/010_notifications/INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_8.class.php
+++ b/plugins/viathinksoft/adminPages/010_notifications/INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_8.class.php
@@ -27,7 +27,7 @@ interface INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_8 {
 
 	/**
 	 * @param string|null $user
-	 * @return array  returns array of array($severity, $htmlMessage)
+	 * @return array  returns array of array($severity, $htmlMessage) where severity can be OK|INFO|WARN|ERR|CRIT.
 	 */
 	public function getNotifications(string $user=null): array;
 
diff --git a/plugins/viathinksoft/adminPages/010_notifications/OIDplusPageAdminNotifications.class.php b/plugins/viathinksoft/adminPages/010_notifications/OIDplusPageAdminNotifications.class.php
index 9e5be909..51c93e45 100644
--- a/plugins/viathinksoft/adminPages/010_notifications/OIDplusPageAdminNotifications.class.php
+++ b/plugins/viathinksoft/adminPages/010_notifications/OIDplusPageAdminNotifications.class.php
@@ -110,7 +110,7 @@ class OIDplusPageAdminNotifications extends OIDplusPagePluginAdmin
 					else if ($severity == 3) $sev_hf = _L('Warnings');
 					else if ($severity == 4) $sev_hf = _L('Errors');
 					else if ($severity == 5) $sev_hf = _L('Critical issues');
-					else $sev_hf = _L('Severity %1', $severity-1);
+					else $sev_hf = _L('Severity %1', $severity-1); // TODO: actually, this should raise an Exception?
 
 					$out['text'] .= '<h2><span class="severity_'.$severity.'">'.$sev_hf.' ('.count($htmlMessages).')</span></h2>';
 					$out['text'] .= '<span class="severity_'.$severity.'"><ol>';
@@ -226,8 +226,11 @@ class OIDplusPageAdminNotifications extends OIDplusPagePluginAdmin
 			}
 
 			// Check if cache directory is writeable
-			if (!is_writeable(OIDplus::localpath(null).'userdata/cache/')) {
-				$notifications[] = array('ERR', _L('Directory %1 is not writeable. Please check the permissions!', 'userdata/cache/'));
+			$cache_dir = OIDplus::localpath(null).'userdata/cache/';
+			if (!is_dir($cache_dir)) {
+				$notifications[] = array('ERR', _L('Directory %1 does not exist', $cache_dir));
+			} else if (!isFileOrPathWritable($cache_dir)) {
+				$notifications[] = array('ERR', _L('Directory %1 is not writeable. Please check the permissions!', $cache_dir));
 			}
 		}
 		return $notifications;
diff --git a/plugins/viathinksoft/adminPages/111_systeminfo/OIDplusPageAdminSysteminfo.class.php b/plugins/viathinksoft/adminPages/111_systeminfo/OIDplusPageAdminSysteminfo.class.php
index 798300fc..9c566b90 100644
--- a/plugins/viathinksoft/adminPages/111_systeminfo/OIDplusPageAdminSysteminfo.class.php
+++ b/plugins/viathinksoft/adminPages/111_systeminfo/OIDplusPageAdminSysteminfo.class.php
@@ -248,50 +248,8 @@ class OIDplusPageAdminSysteminfo extends OIDplusPagePluginAdmin {
 			$out['text'] .= '	</tr>';
 			$out['text'] .= '	<tr>';
 			$out['text'] .= '		<td>'._L('User account').'</td>';
-			$current_user = exec('whoami');
-			if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
-				try {
-					if (function_exists('mb_convert_encoding')) {
-						$current_user = @mb_convert_encoding($current_user, "UTF-8", "cp850");
-					} else if (function_exists('iconv')) {
-						$current_user = @iconv("cp850", "UTF-8", $current_user);
-					}
-				} catch (\Exception $e) {}
-				if (function_exists('mb_strtoupper')) {
-					$current_user = mb_strtoupper($current_user); // just cosmetics
-				}
-			}
-			if ($current_user == '') {
-				if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
-					// Windows on an IIS server:
-					//     getenv('USERNAME')     MARSCHALL$                (That is the "machine account", see https://docs.microsoft.com/en-us/iis/manage/configuring-security/application-pool-identities#accessing-the-network )
-					//     get_current_user()     DefaultAppPool
-					//     exec('whoami')         iis apppool\defaultapppool
-					// Windows with XAMPP:
-					//     getenv('USERNAME')     dmarschall
-					//     get_current_user()     dmarschall               (even if script has a different NTFS owner!)
-					//     exec('whoami')         hickelsoft\dmarschall
-					$current_user = get_current_user();
-					if ($current_user == '') $current_user = getenv('USERNAME');
-				} else {
-					// On Linux:
-					$current_user = exec('id -un');
-					if ($current_user == '') {
-						// get_current_user() will get the owner of the PHP script, not the process owner!
-						// We want the process owner, so we use posix_geteuid().
-						if (function_exists('posix_geteuid') && function_exists('posix_getpwuid')) {
-							$uid = posix_geteuid();
-							$current_user = posix_getpwuid($uid); // receive username (required read access to /etc/passwd )
-							if ($current_user !== false) $current_user = $current_user['name'];
-						} else {
-							$uid = -1;
-						}
-						if ($current_user == '') $current_user = get_current_user();
-						if (($current_user == '') && ($uid >= 0)) $current_user = '#' . $uid;
-					}
-				}
-			}
-			$out['text'] .= '		<td>'.($current_user == '' ? '<i>'._L('unknown').'</i>' : htmlentities($current_user)).'</td>';
+			$current_user = get_own_username();
+			$out['text'] .= '		<td>'.($current_user === false ? '<i>'._L('unknown').'</i>' : htmlentities($current_user)).'</td>';
 			$out['text'] .= '	</tr>';
 			$out['text'] .= '</tbody>';
 			$out['text'] .= '</table>';
diff --git a/plugins/viathinksoft/adminPages/900_software_update/OIDplusPageAdminSoftwareUpdate.class.php b/plugins/viathinksoft/adminPages/900_software_update/OIDplusPageAdminSoftwareUpdate.class.php
index 830e9153..10e2bd5f 100644
--- a/plugins/viathinksoft/adminPages/900_software_update/OIDplusPageAdminSoftwareUpdate.class.php
+++ b/plugins/viathinksoft/adminPages/900_software_update/OIDplusPageAdminSoftwareUpdate.class.php
@@ -141,7 +141,7 @@ class OIDplusPageAdminSoftwareUpdate extends OIDplusPagePluginAdmin
 
 				}
 
-				// All OK! Now write file
+				// All OK! Now write the file
 
 				$tmp_filename = 'update_'.generateRandomString(10).'.tmp.php';
 				$local_file = OIDplus::localpath().$tmp_filename;
diff --git a/plugins/viathinksoft/language/dede/messages.xml b/plugins/viathinksoft/language/dede/messages.xml
index c5ccb6a3..fbda4b68 100644
--- a/plugins/viathinksoft/language/dede/messages.xml
+++ b/plugins/viathinksoft/language/dede/messages.xml
@@ -1812,6 +1812,14 @@
 		Digital Object Identifier (DOI)
 		]]></target>
 	</message>
+	<message>
+		<source><![CDATA[
+		Directory %1 does not exist
+		]]></source>
+		<target><![CDATA[
+		Verzeichnis %1 existiert nicht
+		]]></target>
+	</message>
 	<message>
 		<source><![CDATA[
 		Directory %1 is not writeable. Please check the permissions!
@@ -6892,6 +6900,14 @@
 		Die E-Mail-Adresse %1 wurde nicht bestätigt. Bitte bestätigen Sie sie zuerst!
 		]]></target>
 	</message>
+	<message>
+		<source><![CDATA[
+		The file attachments feature is not available due to a misconfiguration
+		]]></source>
+		<target><![CDATA[
+		Datei-Anhänge sind aufgrund einer Fehlkonfiguration nicht möglich
+		]]></target>
+	</message>
 	<message>
 		<source><![CDATA[
 		The file does not exist
diff --git a/plugins/viathinksoft/publicPages/095_attachments/OIDplusPagePublicAttachments.class.php b/plugins/viathinksoft/publicPages/095_attachments/OIDplusPagePublicAttachments.class.php
index 73cdf770..ecfd0fa8 100644
--- a/plugins/viathinksoft/publicPages/095_attachments/OIDplusPagePublicAttachments.class.php
+++ b/plugins/viathinksoft/publicPages/095_attachments/OIDplusPagePublicAttachments.class.php
@@ -26,7 +26,8 @@ namespace ViaThinkSoft\OIDplus;
 class OIDplusPagePublicAttachments extends OIDplusPagePluginPublic
 	implements INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_2, /* modifyContent */
 	           INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_3, /* beforeObject*, afterObject* */
-	           INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_4  /* whois*Attributes */
+	           INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_4, /* whois*Attributes */
+	           INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_8  /* getNotifications */
 {
 
 	/**
@@ -104,11 +105,10 @@ class OIDplusPagePublicAttachments extends OIDplusPagePluginPublic
 	}
 
 	/**
-	 * @param string|null $id
 	 * @return string
 	 * @throws OIDplusException
 	 */
-	public static function getUploadDir(string $id=null): string {
+	protected static function getUploadBaseDir(): string {
 		// Get base path
 		$cfg = OIDplus::config()->getValue('attachment_upload_dir', '');
 		$cfg = trim($cfg);
@@ -117,6 +117,16 @@ class OIDplusPagePublicAttachments extends OIDplusPagePluginPublic
 		} else {
 			$basepath = $cfg;
 		}
+		return $basepath;
+	}
+
+	/**
+	 * @param string|null $id
+	 * @return string
+	 * @throws OIDplusException
+	 */
+	public static function getUploadDir(string $id=null): string {
+		$basepath = self::getUploadBaseDir();
 
 		try {
 			self::checkUploadDir($basepath);
@@ -601,4 +611,34 @@ class OIDplusPagePublicAttachments extends OIDplusPagePluginPublic
 	 * @return void
 	 */
 	public function whoisRaAttributes(string $email, array &$out) {}
+
+	/**
+	 * Implements interface INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_8
+	 * @param string|null $user
+	 * @return array  returns array of array($severity, $htmlMessage)
+	 */
+	public function getNotifications(string $user=null): array {
+		$notifications = array();
+		if ((!$user || ($user == 'admin')) && OIDplus::authUtils()->isAdminLoggedIn()) {
+			$error = '';
+			try {
+				$basepath = self::getUploadBaseDir();
+				if (!is_dir($basepath)) {
+					throw new OIDplusException(_L('Directory %1 does not exist', $basepath));
+				} else {
+					self::checkUploadDir($basepath);
+					if (!isFileOrPathWritable($basepath)) {
+						throw new OIDplusException(_L('Directory %1 is not writeable. Please check the permissions!', $basepath));
+					}
+				}
+			} catch (\Exception $e) {
+				$error = _L('The file attachments feature is not available due to a misconfiguration');
+				$error .= ': ' . $e->getMessage();
+			}
+			if ($error) {
+				$notifications[] = array('WARN', $error);
+			}
+		}
+		return $notifications;
+	}
 }
-- 
GitLab