*/ function ptcp_yaml_emit( $data ){ $yaml = trim( yaml_emit( $data ) ); if ( substr( $yaml, 0, 4 ) == "---\n" ){ $yaml = substr( $yaml, 4 ); } if ( substr( $yaml, -4 ) == "\n..." ){ $yaml = substr( $yaml, 0, -4 ); } return $yaml; } function ptcp_register_message_handler( $classOrObject, $highPrio = FALSE ){ global $MessageHandlers; if ( $highPrio ){ array_unshift( $MessageHandlers, $classOrObject ); } else { array_push( $MessageHandlers, $classOrObject ); } } function ptcp_register_auth_checker( $classOrObject, $highPrio = FALSE ){ global $AuthCheckers; if ( $highPrio ){ array_unshift( $AuthCheckers, $classOrObject ); } else { array_push( $AuthCheckers, $classOrObject ); } } function ptcp_register_id_provider( $classOrObject, $highPrio = FALSE ){ global $IdProviders; if ( $highPrio ){ array_unshift( $IdProviders, $classOrObject ); } else { array_push( $IdProviders, $classOrObject ); } } function ptcp_error( $text ){ global $PartcpVersion, $ServerData; if ( version_compare( $PartcpVersion, '1.0', '<' ) ){ header( $_SERVER['SERVER_PROTOCOL'] . ' 500 Internal Server Error' ); die( $text ); } $lines[] = "From: {$ServerData['name']}"; $lines[] = 'Message-Type: failure-notice'; $lines[] = "Failure-Description: {$text}"; die( implode( "\n", $lines ) ); } function ptcp_is_in_list( $ptcpId, $ptcpList ){ global $ServerData; if ( ! strpos( $ptcpId, '@' ) ){ $ptcpId .= '@' . $ServerData['name']; } foreach ( $ptcpList as $key => $id ){ if ( ! strpos( $id, '@' ) ){ $ptcpList[ $key ] .= '@' . $ServerData['name']; } } return in_array( $ptcpId, $ptcpList ); } function ptcp_get_id_data( $ptcpId ){ global $IdProviders; foreach ( $IdProviders as $provider ){ $idData = $provider->get_info( $ptcpId ); if ( $idData ){ break; } } return $idData ?? FALSE; } function ptcp_is_authorized( $ptcpId, $messageType, $object = NULL ){ global $AuthCheckers; foreach ( $AuthCheckers as $classOrObject ){ $result = call_user_func( [ $classOrObject, 'is_authorized' ], $ptcpId, $messageType, $object ); if ( $result !== NULL ){ return $result; } } return FALSE; } function ptcp_handle_message( $message, $receipt ){ global $MessageHandlers; foreach ( $MessageHandlers as $classOrObject ){ $methodName = 'handle_' . str_replace( '-', '_', $message->get('Message-Type') ); if ( ! method_exists( $classOrObject, $methodName ) ){ continue; } $result = call_user_func( [ $classOrObject, $methodName ], $message, $receipt ); if ( $result !== FALSE ){ return $result; } } foreach ( $MessageHandlers as $classOrObject ){ if ( ! method_exists( $classOrObject, 'router' ) ){ continue; } $methodName = call_user_func( [ $classOrObject, 'router' ], $message->get('Message-Type') ); if ( $methodName !== FALSE && method_exists( $classOrObject, $methodName ) ){ $result = call_user_func( [ $classOrObject, $methodName ], $message, $receipt ); if ( $result !== FALSE ){ return $result; } } } return FALSE; } function ptcp_subdir_rules( $estimatedTotal ){ global $Config; $itemsPerDirectory = $Config['max_items_per_dir'] ?? $Config['Max-Items-Per-Directory'] ?? 9999; $directoriesNeeded = ceil( $estimatedTotal / $itemsPerDirectory ); $totalPathLength = ceil( log( $directoriesNeeded, 16 ) ); $maxDirNameLength = floor( log( $itemsPerDirectory, 16 ) ); $subdirDepth = ceil( $totalPathLength / $maxDirNameLength ); $subdirNameLength = $subdirDepth ? ceil( $totalPathLength / $subdirDepth ) : 0; return [ (int) $subdirDepth, (int) $subdirNameLength ]; } function ptcp_update_object( &$object, $newData ){ foreach ( $newData as $key => $value ){ if ( is_null( $value ) ){ unset( $object[ $key ] ); } elseif ( is_array( $value ) && isset( $object[ $key ] ) && is_array( $object[ $key ] ) && count( array_filter( array_keys( $value ), 'is_string' ) ) > 0 ){ ptcp_update_object( $object[ $key ], $value ); } else { $object[ $key ] = $value; } } } function ptcp_local_encrypt( $value ){ global $ServerData; static $secret; if ( empty( $secret ) ){ $keyPair = ParTCP_Server_Key_Storage::get_keypair( $ServerData['name'] ); $secret = hash( 'sha256', $keyPair[1], TRUE ); } if ( is_array( $value ) ){ $value = json_encode( $value ); } $iv = openssl_random_pseudo_bytes( 16 ); $encrypted = openssl_encrypt( $value, 'aes-256-cbc', $secret, 0, $iv ); return base64_encode( $iv ) . ':' . $encrypted; } function ptcp_local_decrypt( $string ){ global $ServerData; static $secret; if ( empty( $secret ) ){ $keyPair = ParTCP_Server_Key_Storage::get_keypair( $ServerData['name'] ); $secret = hash( 'sha256', $keyPair[1], TRUE ); } list( $iv, $encrypted ) = explode( ':', $string ) + [ '', '' ]; $iv = base64_decode( $iv ); if ( ! $iv || strlen( $iv ) != 16 ){ return $string; } return openssl_decrypt( $encrypted, 'aes-256-cbc', $secret, 0, $iv ); } // Main script error_reporting( E_ALL ); ini_set( 'display_errors', 1 ); $Timestamp = time(); $startTime = microtime( TRUE ); $BaseDir = __DIR__; $RequestHeaders = function_exists('apache_request_headers') ? array_change_key_case( apache_request_headers() ) : []; $PartcpVersion = $RequestHeaders['x-partcp-version'] ?? '0.8'; $MessageHandlers = []; $AuthCheckers = []; $IdProviders = []; $Counter = NULL; $Shortcoder = NULL; header('Content-Type: text/plain'); require_once 'lib/partcp-php/crypto.class.php'; ParTCP_Crypto::$useLegacyKx = empty( $RequestHeaders['x-partcp-kx-method'] ); require_once 'lib/partcp-php/key_storage_fs.class.php'; require_once 'lib/partcp-php/identity.class.php'; require_once 'lib/partcp-php/incoming_message.class.php'; require_once 'lib/partcp-php/outgoing_message.class.php'; require_once 'lib/server_key_storage.class.php'; require_once 'lib/mtd_manager.class.php'; include 'config.php'; include 'init/init_filesystem.php'; $MtdManager = new Mtd_Manager( [ __DIR__ . '/mtd' ] ); $ServerData = $Config['server_data'] ?? $Config['Server-Data'] ?? []; if ( empty( $ServerData['name'] ) ){ $ServerData['name'] = $_SERVER['SERVER_NAME']; } // Load modules if ( ! empty( $ServerData['modules'] ) ){ foreach ( $ServerData['modules'] as $module ){ $path = "{$BaseDir}/modules/{$module}/init.php"; if ( file_exists( $path ) ){ include $path; } else { ptcp_error( sprintf( _('Module %s could not be initialized'), $module ) ); } } } // Initialize localisation include 'init/init_locale.php'; // Throw error, if maintenance mode is active if ( ! empty( $ServerData['maintenance_mode'] ) ){ ptcp_error( _('Maintenance mode is active') ); } // Handle special use cases if ( isset( $argv[1] ) && $argv[1] == 'cron' && posix_geteuid() == 0 && empty( $ServerData['maintenance_mode'] ) ){ // Execute cron tasks, ensure that no other cron process runs in parallel $sem = sem_get( 230608, 1 ); if ( ! $sem || ! sem_acquire( $sem, TRUE ) ){ exit; } foreach ( $ServerData['modules'] as $module ){ $path = "{$BaseDir}/modules/{$module}/cron.php"; if ( file_exists( $path ) ){ include $path; } } sem_release( $sem ); exit; } elseif ( empty( $_POST['message'] ) ){ $_POST['message'] = file_get_contents('php://input'); if ( empty( $_POST['message'] ) ){ // Nothing to process, show contents of this script file readfile( __FILE__ ); exit; } } // Initialize identity module $dir = $Config['path_to_keys'] ?? $Config['Key-Directory'] ?? NULL; if ( ! $dir ){ ptcp_error( _('Key directory not specified') ); } ParTCP_Key_Storage_Fs::$storageDir = $dir; ParTCP_Identity::$storage = 'ParTCP_Server_Key_Storage'; // Prepare processing $localId = new ParTCP_Private_Identity( $ServerData['name'], TRUE, TRUE ); $Message = new ParTCP_Incoming_Message( $_POST['message'] ); if ( ! empty( $Message->parseError ) ){ $Receipt->set_rejection( 10, _('Message could not be parsed') . ":\n" . $Message->parseError ); die( $Receipt->dump( TRUE ) ); } $id = $Message->get('From'); $retrievePubKey = ! empty( $id ) && ! empty( $Message->get('Signature') ); $remoteId = new ParTCP_Public_Identity( $id ?: '', $retrievePubKey ); if ( empty( $remoteId->pubKey ) && $key = $Message->get('Public-Key') ){ $remoteId->pubKey = $key; } $Receipt = new ParTCP_Outgoing_Message( $remoteId, $localId ); $Receipt->set_date(); $Receipt->set( 'Original-Message', $_POST['message'] ); // Process message and deliver receipt if ( ! $Message->get('Message-Type') ){ $Receipt->set_rejection( 11, _('Message-Type header is missing') ); die( $Receipt->dump( TRUE ) ); } $mtd = $MtdManager->get_mtd( $Message->get('Message-Type') ); if ( ! empty( $MtdManager->lastError ) ){ ptcp_error( sprintf( _('MTD for %s could not be parsed'), $Message->get('Message-Type') ) . ":\n{$MtdManager->lastError}" ); } if ( empty( $mtd['Name'] ) || $mtd['Type'] != 'request' ){ $Receipt->set_rejection( 12, sprintf( _('Unknown message type \'%s\''), $Message->get('Message-Type') ) ); die( $Receipt->dump( TRUE ) ); } if ( ! $Message->validate_structure( $mtd ) ){ $Receipt->set_rejection( 13, _('Invalid message structure') . ":\n" . implode( "\n", $Message->validationResult ) ); die( $Receipt->dump( TRUE ) ); } $trust = ! empty( $mtd['Elements']['Signature']['trust_embedded_key'] ) || empty( $Message->get('From') ); if ( $Message->get('Signature') && ! $Message->get_signature_status( $trust ) ){ $Receipt->set_rejection( 71, _('Signature could not be verified') . " - {$Message->signatureStatusMessage}" ); die( $Receipt->dump( TRUE ) ); } if ( ! empty( $Message->decryptionError ) ){ $Receipt->set_rejection( 72, _('Elements could not be decrypted') . ":\n" . $Message->decryptionError ); die( $Receipt->dump( TRUE ) ); } $Receipt->set( 'Message-Type', $mtd['Response'] ?? 'receipt' ); $result = ptcp_handle_message( $Message, $Receipt ); if ( $result === FALSE ){ ptcp_error( sprintf( _('No message handler found for \'%s\''), $Message->get('Message-Type') ) ); } if ( version_compare( $PartcpVersion, '1.0', '<' ) ){ $tmpMsg = yaml_parse( $result ); if ( $tmpMsg['Message-Type'] == 'rejection-notice' ){ header( $_SERVER['SERVER_PROTOCOL'] . ' 400 Bad Request' ); } elseif ( $tmpMsg['Message-Type'] == 'failure-notice' ){ header( $_SERVER['SERVER_PROTOCOL'] . ' 500 Internal Server Error' ); } die( $result ); } header( 'X-Elapsed-Time: ' . sprintf( '%.3f ms', 1000 * ( microtime( TRUE ) - $startTime ) ) ); echo $result; // end of file process.php