diff --git a/README.md b/README.md
index e9c861c4fb5e36b19df5df12413a1f5fa64becff..c3e8d716c1884045fcd765ad1e590305c2bca35b 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 103fe121616a918776aa725be6ee28f367d7cb15..0a5045cd5dbf22ce0c56dbb16333a10431cf60a4 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 dbed65ac1edf4314ec33a8d9db9c5e54b2098af1..070689546fb6b802561a892b0a9728dfd6a476e4 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 d3541a7b03e111ecef13af58fe956a74559e1a45..fd77366bf837c2cfe56cc717d11db79617b139ba 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 9e5be909762226af855541276c1b29b7846c5f71..51c93e45dcc48c0c49df8b571d772dd052159970 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 798300fcb293821c50e6053dbe1112ab6edb99b0..9c566b90a89987f6c8ba40efc26d3f396db50609 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 830e91533d87d60a3596abe4f3ab55b3f96d0756..10e2bd5fbfcfb98a299c452fc0ebdc4ca28cc857 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 c5ccb6a3b6b7dcbc94d90ac2368633047aed355e..fbda4b68010ef899517cc95e40842bc5ab0be539 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 73cdf770fa9dee6dbf16f19714e01c7d67ae1d5f..ecfd0fa89b197f09f1ad784303cdcc97fc449497 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;
+	}
 }