passenger 5.0.9 → 5.0.10
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of passenger might be problematic. Click here for more details.
- checksums.yaml +8 -8
- checksums.yaml.gz.asc +7 -7
- data.tar.gz.asc +7 -7
- data/CHANGELOG +15 -0
- data/CONTRIBUTORS +6 -0
- data/README.md +1 -1
- data/bin/passenger-install-apache2-module +24 -11
- data/bin/passenger-status +29 -14
- data/build/agents.rb +12 -10
- data/build/cxx_tests.rb +30 -30
- data/doc/Design and Architecture.html +1 -10
- data/doc/Design and Architecture.txt +1 -6
- data/doc/Users guide Apache.html +1 -19
- data/doc/Users guide Apache.txt +1 -1
- data/doc/Users guide Nginx.html +2 -20
- data/doc/Users guide Nginx.txt +2 -2
- data/doc/users_guide_snippets/tips.txt +0 -9
- data/ext/common/ApplicationPool2/ApiKey.h +158 -0
- data/ext/common/ApplicationPool2/BasicGroupInfo.h +81 -0
- data/ext/common/ApplicationPool2/BasicProcessInfo.h +106 -0
- data/ext/common/ApplicationPool2/Common.h +5 -44
- data/ext/common/ApplicationPool2/Context.h +94 -0
- data/ext/common/ApplicationPool2/Group.h +130 -1205
- data/ext/common/ApplicationPool2/Group/InitializationAndShutdown.cpp +190 -0
- data/ext/common/ApplicationPool2/Group/InternalUtils.cpp +329 -0
- data/ext/common/ApplicationPool2/Group/LifetimeAndBasics.cpp +103 -0
- data/ext/common/ApplicationPool2/{Pool/Debug.h → Group/Miscellaneous.cpp} +40 -38
- data/ext/common/ApplicationPool2/Group/OutOfBandWork.cpp +323 -0
- data/ext/common/ApplicationPool2/Group/ProcessListManagement.cpp +606 -0
- data/ext/common/ApplicationPool2/Group/SessionManagement.cpp +337 -0
- data/ext/common/ApplicationPool2/Group/SpawningAndRestarting.cpp +478 -0
- data/ext/common/ApplicationPool2/Group/StateInspection.cpp +197 -0
- data/ext/common/ApplicationPool2/Group/Verification.cpp +159 -0
- data/ext/common/ApplicationPool2/Implementation.cpp +19 -1401
- data/ext/common/ApplicationPool2/Options.h +5 -5
- data/ext/common/ApplicationPool2/Pool.h +260 -815
- data/ext/common/ApplicationPool2/Pool/{AnalyticsCollection.h → AnalyticsCollection.cpp} +55 -56
- data/ext/common/ApplicationPool2/Pool/{GarbageCollection.h → GarbageCollection.cpp} +49 -49
- data/ext/common/ApplicationPool2/Pool/GeneralUtils.cpp +241 -0
- data/ext/common/ApplicationPool2/Pool/GroupUtils.cpp +276 -0
- data/ext/common/ApplicationPool2/Pool/InitializationAndShutdown.cpp +145 -0
- data/ext/common/ApplicationPool2/Pool/Miscellaneous.cpp +244 -0
- data/ext/common/ApplicationPool2/Pool/ProcessUtils.cpp +330 -0
- data/ext/common/ApplicationPool2/Pool/StateInspection.cpp +299 -0
- data/ext/common/ApplicationPool2/Process.h +399 -205
- data/ext/common/ApplicationPool2/Session.h +70 -28
- data/ext/common/ApplicationPool2/Socket.h +1 -0
- data/ext/common/Constants.h +11 -3
- data/ext/common/Exceptions.h +1 -1
- data/ext/common/Logging.cpp +9 -4
- data/ext/common/Logging.h +6 -0
- data/ext/common/ServerKit/HttpServer.h +225 -215
- data/ext/common/ServerKit/Server.h +57 -57
- data/ext/common/SpawningKit/BackgroundIOCapturer.h +160 -0
- data/ext/common/SpawningKit/Config.h +107 -0
- data/ext/common/{ApplicationPool2 → SpawningKit}/DirectSpawner.h +17 -16
- data/ext/common/{ApplicationPool2 → SpawningKit}/DummySpawner.h +33 -33
- data/ext/common/{ApplicationPool2/SpawnerFactory.h → SpawningKit/Factory.h} +17 -17
- data/ext/common/{ApplicationPool2/ComponentInfo.h → SpawningKit/Options.h} +8 -21
- data/ext/common/SpawningKit/PipeWatcher.h +148 -0
- data/ext/common/{ApplicationPool2/PipeWatcher.h → SpawningKit/Result.h} +15 -33
- data/ext/common/{ApplicationPool2 → SpawningKit}/SmartSpawner.h +52 -57
- data/ext/common/{ApplicationPool2 → SpawningKit}/Spawner.h +83 -371
- data/ext/common/SpawningKit/UserSwitchingRules.h +265 -0
- data/ext/common/Utils/BufferedIO.h +24 -0
- data/ext/common/{ApplicationPool2/SpawnObject.h → Utils/ClassUtils.h} +24 -51
- data/ext/common/Utils/IOUtils.cpp +70 -0
- data/ext/common/Utils/IOUtils.h +19 -0
- data/ext/common/Utils/JsonUtils.h +113 -0
- data/ext/common/Utils/StrIntUtils.h +29 -0
- data/ext/common/Utils/json.h +1 -1
- data/ext/common/agents/ApiServerUtils.h +941 -0
- data/ext/common/agents/HelperAgent/{AdminServer.h → ApiServer.h} +163 -365
- data/ext/common/agents/HelperAgent/Main.cpp +86 -88
- data/ext/common/agents/HelperAgent/OptionParser.h +9 -10
- data/ext/common/agents/HelperAgent/RequestHandler/BufferBody.cpp +3 -0
- data/ext/common/agents/HelperAgent/RequestHandler/ForwardResponse.cpp +2 -0
- data/ext/common/agents/HelperAgent/RequestHandler/Hooks.cpp +1 -1
- data/ext/common/agents/HelperAgent/RequestHandler/SendRequest.cpp +2 -2
- data/ext/common/agents/LoggingAgent/ApiServer.h +279 -0
- data/ext/common/agents/LoggingAgent/Main.cpp +41 -51
- data/ext/common/agents/LoggingAgent/OptionParser.h +11 -11
- data/ext/common/agents/Watchdog/ApiServer.h +311 -0
- data/ext/common/agents/Watchdog/Main.cpp +91 -65
- data/helper-scripts/prespawn +2 -0
- data/lib/phusion_passenger.rb +1 -1
- data/lib/phusion_passenger/admin_tools/instance.rb +1 -1
- data/lib/phusion_passenger/common_library.rb +27 -14
- data/lib/phusion_passenger/config/{admin_command_command.rb → api_call_command.rb} +19 -16
- data/lib/phusion_passenger/config/detach_process_command.rb +6 -3
- data/lib/phusion_passenger/config/main.rb +3 -5
- data/lib/phusion_passenger/config/reopen_logs_command.rb +29 -7
- data/lib/phusion_passenger/config/restart_app_command.rb +13 -4
- data/lib/phusion_passenger/config/utils.rb +15 -8
- data/lib/phusion_passenger/constants.rb +6 -2
- data/lib/phusion_passenger/platform_info/apache.rb +4 -0
- data/lib/phusion_passenger/platform_info/apache_detector.rb +18 -3
- data/resources/templates/apache2/mpm_unknown.txt.erb +20 -0
- metadata +42 -21
- metadata.gz.asc +7 -7
- data/ext/common/ApplicationPool2/Pool/GeneralUtils.h +0 -127
- data/ext/common/ApplicationPool2/Pool/Inspection.h +0 -219
- data/ext/common/ApplicationPool2/Pool/ProcessUtils.h +0 -85
- data/ext/common/ApplicationPool2/SuperGroup.h +0 -706
- data/ext/common/agents/LoggingAgent/AdminServer.h +0 -435
- data/ext/common/agents/Watchdog/AdminServer.h +0 -432
data/ext/common/Utils/IOUtils.h
CHANGED
@@ -94,6 +94,14 @@ void parseTcpSocketAddress(const StaticString & restrict_ref address,
|
|
94
94
|
*/
|
95
95
|
bool isLocalSocketAddress(const StaticString &address);
|
96
96
|
|
97
|
+
/**
|
98
|
+
* Sets a socket in blocking mode.
|
99
|
+
*
|
100
|
+
* @throws SystemException Something went wrong.
|
101
|
+
* @ingroup Support
|
102
|
+
*/
|
103
|
+
void setBlocking(int fd);
|
104
|
+
|
97
105
|
/**
|
98
106
|
* Sets a socket in non-blocking mode.
|
99
107
|
*
|
@@ -591,6 +599,17 @@ int readFileDescriptor(int fd, unsigned long long *timeout = NULL);
|
|
591
599
|
*/
|
592
600
|
void writeFileDescriptor(int fd, int fdToSend, unsigned long long *timeout = NULL);
|
593
601
|
|
602
|
+
/**
|
603
|
+
* Return the effective UID and GID of the peer connected to a Unix domain socket.
|
604
|
+
*
|
605
|
+
* @throws SystemException Something went wrong. If reading credentials over a Unix
|
606
|
+
* domain socket is not supported for the current platform,
|
607
|
+
* then the error code is ENOSYS. If the socket does not
|
608
|
+
* support credentials passing, then the error code is
|
609
|
+
* EPROTONOSUPPORT.
|
610
|
+
*/
|
611
|
+
void readPeerCredentials(int sock, uid_t *uid, gid_t *gid);
|
612
|
+
|
594
613
|
/**
|
595
614
|
* Closes the given file descriptor and throws an exception if anything goes wrong.
|
596
615
|
* This function also works around certain close() bugs and quirks on certain
|
@@ -28,15 +28,128 @@
|
|
28
28
|
#include <string>
|
29
29
|
#include <cstdio>
|
30
30
|
#include <cstddef>
|
31
|
+
#include <boost/cstdint.hpp>
|
31
32
|
#include <StaticString.h>
|
32
33
|
#include <Utils/json.h>
|
33
34
|
#include <Utils/StrIntUtils.h>
|
35
|
+
#include <Utils/VariantMap.h>
|
34
36
|
|
35
37
|
namespace Passenger {
|
36
38
|
|
37
39
|
using namespace std;
|
38
40
|
|
39
41
|
|
42
|
+
/**************************************************************
|
43
|
+
*
|
44
|
+
* Methods for querying fields from a JSON document.
|
45
|
+
* If the field is missing, thhese methods can either return
|
46
|
+
* a default value, or throw an exception.
|
47
|
+
*
|
48
|
+
**************************************************************/
|
49
|
+
|
50
|
+
inline const Json::Value &
|
51
|
+
getJsonField(const Json::Value &json, const char *key) {
|
52
|
+
Json::StaticString theKey(key);
|
53
|
+
if (json.isMember(theKey)) {
|
54
|
+
return json[theKey];
|
55
|
+
} else {
|
56
|
+
throw VariantMap::MissingKeyException(key);
|
57
|
+
}
|
58
|
+
}
|
59
|
+
|
60
|
+
inline Json::Value &
|
61
|
+
getJsonField(Json::Value &json, const char *key) {
|
62
|
+
Json::StaticString theKey(key);
|
63
|
+
if (json.isMember(theKey)) {
|
64
|
+
return json[theKey];
|
65
|
+
} else {
|
66
|
+
throw VariantMap::MissingKeyException(key);
|
67
|
+
}
|
68
|
+
}
|
69
|
+
|
70
|
+
inline int
|
71
|
+
getJsonIntField(const Json::Value &json, const char *key) {
|
72
|
+
Json::StaticString theKey(key);
|
73
|
+
if (json.isMember(theKey)) {
|
74
|
+
return json[theKey].asInt();
|
75
|
+
} else {
|
76
|
+
throw VariantMap::MissingKeyException(key);
|
77
|
+
}
|
78
|
+
}
|
79
|
+
|
80
|
+
inline int
|
81
|
+
getJsonIntField(const Json::Value &json, const char *key, int defaultValue) {
|
82
|
+
Json::StaticString theKey(key);
|
83
|
+
if (json.isMember(theKey)) {
|
84
|
+
return json[theKey].asInt();
|
85
|
+
} else {
|
86
|
+
return defaultValue;
|
87
|
+
}
|
88
|
+
}
|
89
|
+
|
90
|
+
inline unsigned int
|
91
|
+
getJsonUintField(const Json::Value &json, const char *key) {
|
92
|
+
Json::StaticString theKey(key);
|
93
|
+
if (json.isMember(theKey)) {
|
94
|
+
return json[theKey].asUInt();
|
95
|
+
} else {
|
96
|
+
throw VariantMap::MissingKeyException(key);
|
97
|
+
}
|
98
|
+
}
|
99
|
+
|
100
|
+
inline unsigned int
|
101
|
+
getJsonUintField(const Json::Value &json, const char *key, unsigned int defaultValue) {
|
102
|
+
Json::StaticString theKey(key);
|
103
|
+
if (json.isMember(theKey)) {
|
104
|
+
return json[theKey].asUInt();
|
105
|
+
} else {
|
106
|
+
return defaultValue;
|
107
|
+
}
|
108
|
+
}
|
109
|
+
|
110
|
+
inline boost::uint64_t
|
111
|
+
getJsonUint64Field(const Json::Value &json, const char *key) {
|
112
|
+
Json::StaticString theKey(key);
|
113
|
+
if (json.isMember(theKey)) {
|
114
|
+
return json[theKey].asUInt64();
|
115
|
+
} else {
|
116
|
+
throw VariantMap::MissingKeyException(key);
|
117
|
+
}
|
118
|
+
}
|
119
|
+
|
120
|
+
inline boost::uint64_t
|
121
|
+
getJsonUint64Field(const Json::Value &json, const char *key, unsigned int defaultValue) {
|
122
|
+
Json::StaticString theKey(key);
|
123
|
+
if (json.isMember(theKey)) {
|
124
|
+
return json[theKey].asUInt64();
|
125
|
+
} else {
|
126
|
+
return defaultValue;
|
127
|
+
}
|
128
|
+
}
|
129
|
+
|
130
|
+
inline StaticString
|
131
|
+
getJsonStaticStringField(const Json::Value &json, const char *key) {
|
132
|
+
Json::StaticString theKey(key);
|
133
|
+
if (json.isMember(theKey)) {
|
134
|
+
return json[theKey].asCString();
|
135
|
+
} else {
|
136
|
+
throw VariantMap::MissingKeyException(key);
|
137
|
+
}
|
138
|
+
}
|
139
|
+
|
140
|
+
inline StaticString
|
141
|
+
getJsonStaticStringField(const Json::Value &json, const char *key,
|
142
|
+
const StaticString &defaultValue)
|
143
|
+
{
|
144
|
+
Json::StaticString theKey(key);
|
145
|
+
if (json.isMember(theKey)) {
|
146
|
+
return json[theKey].asCString();
|
147
|
+
} else {
|
148
|
+
return defaultValue;
|
149
|
+
}
|
150
|
+
}
|
151
|
+
|
152
|
+
|
40
153
|
/**************************************************************
|
41
154
|
*
|
42
155
|
* Methods for generating JSON.
|
@@ -34,6 +34,7 @@
|
|
34
34
|
#include <cstdlib>
|
35
35
|
#include <cstddef>
|
36
36
|
#include <ctime>
|
37
|
+
#include <boost/move/utility.hpp>
|
37
38
|
#include <oxt/macros.hpp>
|
38
39
|
#include <StaticString.h>
|
39
40
|
|
@@ -47,11 +48,20 @@ using namespace std;
|
|
47
48
|
* Upon destruction of a DynamicBuffer, the memory buffer is freed.
|
48
49
|
*/
|
49
50
|
struct DynamicBuffer {
|
51
|
+
private:
|
52
|
+
BOOST_MOVABLE_BUT_NOT_COPYABLE(DynamicBuffer)
|
53
|
+
|
54
|
+
public:
|
50
55
|
typedef string::size_type size_type;
|
51
56
|
|
52
57
|
char *data;
|
53
58
|
size_type size;
|
54
59
|
|
60
|
+
DynamicBuffer()
|
61
|
+
: data(NULL),
|
62
|
+
size(0)
|
63
|
+
{ }
|
64
|
+
|
55
65
|
/**
|
56
66
|
* @throws std::bad_alloc The buffer cannot be allocated.
|
57
67
|
*/
|
@@ -64,9 +74,28 @@ struct DynamicBuffer {
|
|
64
74
|
}
|
65
75
|
}
|
66
76
|
|
77
|
+
DynamicBuffer(BOOST_RV_REF(DynamicBuffer) other)
|
78
|
+
: data(other.data),
|
79
|
+
size(other.size)
|
80
|
+
{
|
81
|
+
other.data = NULL;
|
82
|
+
other.size = 0;
|
83
|
+
}
|
84
|
+
|
67
85
|
~DynamicBuffer() throw() {
|
68
86
|
free(data);
|
69
87
|
}
|
88
|
+
|
89
|
+
DynamicBuffer &operator=(BOOST_RV_REF(DynamicBuffer) other) {
|
90
|
+
if (this != &other) {
|
91
|
+
free(data);
|
92
|
+
data = other.data;
|
93
|
+
size = other.size;
|
94
|
+
other.data = NULL;
|
95
|
+
other.size = 0;
|
96
|
+
}
|
97
|
+
return *this;
|
98
|
+
}
|
70
99
|
};
|
71
100
|
|
72
101
|
|
data/ext/common/Utils/json.h
CHANGED
@@ -0,0 +1,941 @@
|
|
1
|
+
/*
|
2
|
+
* Phusion Passenger - https://www.phusionpassenger.com/
|
3
|
+
* Copyright (c) 2015 Phusion
|
4
|
+
*
|
5
|
+
* "Phusion Passenger" is a trademark of Hongli Lai & Ninh Bui.
|
6
|
+
*
|
7
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
8
|
+
* of this software and associated documentation files (the "Software"), to deal
|
9
|
+
* in the Software without restriction, including without limitation the rights
|
10
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
11
|
+
* copies of the Software, and to permit persons to whom the Software is
|
12
|
+
* furnished to do so, subject to the following conditions:
|
13
|
+
*
|
14
|
+
* The above copyright notice and this permission notice shall be included in
|
15
|
+
* all copies or substantial portions of the Software.
|
16
|
+
*
|
17
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
18
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
19
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
20
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
21
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
22
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
23
|
+
* THE SOFTWARE.
|
24
|
+
*/
|
25
|
+
#ifndef _PASSENGER_API_SERVER_UTILS_H_
|
26
|
+
#define _PASSENGER_API_SERVER_UTILS_H_
|
27
|
+
|
28
|
+
/**
|
29
|
+
* Utility code shared by HelperAgent/ApiServer.h, LoggingAgent/ApiServer.h
|
30
|
+
* and Watchdog/ApiServer.h. This code handles authentication and authorization
|
31
|
+
* of connected ApiServer clients.
|
32
|
+
*
|
33
|
+
* This file consists of the following items.
|
34
|
+
*
|
35
|
+
* ## API accounts
|
36
|
+
*
|
37
|
+
* API servers can be password protected. They support multiple accounts,
|
38
|
+
* each with its own privilege level. These accounts are represented by
|
39
|
+
* ApiAccount, stored in ApiAccountDatabase objects.
|
40
|
+
*
|
41
|
+
* ## Authorization
|
42
|
+
*
|
43
|
+
* The authorizeXXX() family of functions implement authorization checking on a
|
44
|
+
* connected client. Given a client and a request, they perform various
|
45
|
+
* checks and return information on what the client is authorized to do.
|
46
|
+
*
|
47
|
+
* ## Utility
|
48
|
+
*
|
49
|
+
* Various utility functions
|
50
|
+
*
|
51
|
+
* ## Common endpoints
|
52
|
+
*
|
53
|
+
* The apiServerProcessXXX() family of functions implement common endpoints
|
54
|
+
* in the various API servers.
|
55
|
+
*/
|
56
|
+
|
57
|
+
#include <boost/foreach.hpp>
|
58
|
+
#include <boost/bind.hpp>
|
59
|
+
#include <boost/regex.hpp>
|
60
|
+
#include <oxt/macros.hpp>
|
61
|
+
#include <oxt/backtrace.hpp>
|
62
|
+
#include <oxt/thread.hpp>
|
63
|
+
#include <sys/types.h>
|
64
|
+
#include <string>
|
65
|
+
#include <vector>
|
66
|
+
#include <cstddef>
|
67
|
+
#include <cstring>
|
68
|
+
#include <ApplicationPool2/Pool.h>
|
69
|
+
#include <ApplicationPool2/ApiKey.h>
|
70
|
+
#include <StaticString.h>
|
71
|
+
#include <Exceptions.h>
|
72
|
+
#include <DataStructures/LString.h>
|
73
|
+
#include <DataStructures/StringKeyTable.h>
|
74
|
+
#include <ServerKit/Server.h>
|
75
|
+
#include <ServerKit/HeaderTable.h>
|
76
|
+
#include <Utils.h>
|
77
|
+
#include <Utils/IOUtils.h>
|
78
|
+
#include <Utils/BufferedIO.h>
|
79
|
+
#include <Utils/StrIntUtils.h>
|
80
|
+
#include <Utils/modp_b64.h>
|
81
|
+
#include <Utils/json.h>
|
82
|
+
#include <Utils/VariantMap.h>
|
83
|
+
|
84
|
+
namespace Passenger {
|
85
|
+
|
86
|
+
using namespace std;
|
87
|
+
|
88
|
+
|
89
|
+
// Forward declarations
|
90
|
+
inline string truncateApiKey(const StaticString &apiKey);
|
91
|
+
|
92
|
+
|
93
|
+
/*******************************
|
94
|
+
*
|
95
|
+
* API accounts
|
96
|
+
*
|
97
|
+
*******************************/
|
98
|
+
|
99
|
+
struct ApiAccount {
|
100
|
+
string username;
|
101
|
+
string password;
|
102
|
+
bool readonly;
|
103
|
+
};
|
104
|
+
|
105
|
+
class ApiAccountDatabase {
|
106
|
+
private:
|
107
|
+
vector<ApiAccount> database;
|
108
|
+
|
109
|
+
bool levelDescriptionIsReadOnly(const StaticString &level) const {
|
110
|
+
if (level == "readonly") {
|
111
|
+
return true;
|
112
|
+
} else if (level == "full") {
|
113
|
+
return false;
|
114
|
+
} else {
|
115
|
+
throw ArgumentException("Invalid privilege level " + level);
|
116
|
+
}
|
117
|
+
}
|
118
|
+
|
119
|
+
public:
|
120
|
+
/**
|
121
|
+
* Add an account to the database with the given parameters.
|
122
|
+
*
|
123
|
+
* @throws ArgumentException One if the input arguments contain a disallowed value.
|
124
|
+
*/
|
125
|
+
void add(const string &username, const string &password, bool readonly) {
|
126
|
+
if (OXT_UNLIKELY(username == "api")) {
|
127
|
+
throw ArgumentException("It is not allowed to register an API account with username 'api'");
|
128
|
+
}
|
129
|
+
|
130
|
+
ApiAccount account;
|
131
|
+
account.username = username;
|
132
|
+
account.password = password;
|
133
|
+
account.readonly = readonly;
|
134
|
+
database.push_back(account);
|
135
|
+
}
|
136
|
+
|
137
|
+
/**
|
138
|
+
* Add an account to the database. The account parameters are determined
|
139
|
+
* by a description string in the form of [LEVEL]:USERNAME:PASSWORDFILE.
|
140
|
+
* LEVEL is one of:
|
141
|
+
*
|
142
|
+
* readonly Read-only access
|
143
|
+
* full Full access (default)
|
144
|
+
*
|
145
|
+
* @throws ArgumentException One if the input arguments contain a disallowed value.
|
146
|
+
*/
|
147
|
+
void add(const StaticString &description) {
|
148
|
+
ApiAccount account;
|
149
|
+
vector<string> args;
|
150
|
+
|
151
|
+
split(description, ':', args);
|
152
|
+
|
153
|
+
if (args.size() == 2) {
|
154
|
+
account.username = args[0];
|
155
|
+
account.password = strip(readAll(args[1]));
|
156
|
+
account.readonly = false;
|
157
|
+
} else if (args.size() == 3) {
|
158
|
+
account.username = args[1];
|
159
|
+
account.password = strip(readAll(args[2]));
|
160
|
+
account.readonly = levelDescriptionIsReadOnly(args[0]);
|
161
|
+
} else {
|
162
|
+
throw ArgumentException("Invalid authorization description '" + description + "'");
|
163
|
+
}
|
164
|
+
|
165
|
+
if (OXT_UNLIKELY(account.username == "api")) {
|
166
|
+
throw ArgumentException("It is not allowed to register an API account with username 'api'");
|
167
|
+
}
|
168
|
+
database.push_back(account);
|
169
|
+
}
|
170
|
+
|
171
|
+
bool empty() const {
|
172
|
+
return database.empty();
|
173
|
+
}
|
174
|
+
|
175
|
+
const ApiAccount *lookup(const StaticString &username) const {
|
176
|
+
vector<ApiAccount>::const_iterator it, end = database.end();
|
177
|
+
|
178
|
+
for (it = database.begin(); it != end; it++) {
|
179
|
+
if (it->username == username) {
|
180
|
+
return &(*it);
|
181
|
+
}
|
182
|
+
}
|
183
|
+
|
184
|
+
return NULL;
|
185
|
+
}
|
186
|
+
};
|
187
|
+
|
188
|
+
|
189
|
+
/*******************************
|
190
|
+
*
|
191
|
+
* Authorization functions
|
192
|
+
*
|
193
|
+
*******************************/
|
194
|
+
|
195
|
+
|
196
|
+
struct Authorization {
|
197
|
+
uid_t uid;
|
198
|
+
ApplicationPool2::ApiKey apiKey;
|
199
|
+
bool canReadPool;
|
200
|
+
bool canModifyPool;
|
201
|
+
bool canInspectState;
|
202
|
+
bool canAdminister;
|
203
|
+
|
204
|
+
Authorization()
|
205
|
+
: uid(-1),
|
206
|
+
canReadPool(false),
|
207
|
+
canModifyPool(false),
|
208
|
+
canInspectState(false),
|
209
|
+
canAdminister(false)
|
210
|
+
{ }
|
211
|
+
};
|
212
|
+
|
213
|
+
|
214
|
+
template<typename Request>
|
215
|
+
inline bool
|
216
|
+
parseBasicAuthHeader(Request *req, string &username, string &password) {
|
217
|
+
const LString *auth = req->headers.lookup("authorization");
|
218
|
+
|
219
|
+
if (auth == NULL || auth->size <= 6 || !psg_lstr_cmp(auth, "Basic ", 6)) {
|
220
|
+
return false;
|
221
|
+
}
|
222
|
+
|
223
|
+
auth = psg_lstr_make_contiguous(auth, req->pool);
|
224
|
+
string authData = modp::b64_decode(
|
225
|
+
auth->start->data + sizeof("Basic ") - 1,
|
226
|
+
auth->size - (sizeof("Basic ") - 1));
|
227
|
+
string::size_type pos = authData.find(':');
|
228
|
+
if (pos == string::npos) {
|
229
|
+
return false;
|
230
|
+
}
|
231
|
+
|
232
|
+
username = authData.substr(0, pos);
|
233
|
+
password = authData.substr(pos + 1);
|
234
|
+
return true;
|
235
|
+
}
|
236
|
+
|
237
|
+
/*
|
238
|
+
* @throws oxt::tracable_exception
|
239
|
+
*/
|
240
|
+
template<typename ApiServer, typename Client, typename Request>
|
241
|
+
inline Authorization
|
242
|
+
authorize(ApiServer *server, Client *client, Request *req) {
|
243
|
+
TRACE_POINT();
|
244
|
+
Authorization auth;
|
245
|
+
uid_t uid = -1;
|
246
|
+
gid_t gid = -1;
|
247
|
+
string username, password;
|
248
|
+
|
249
|
+
try {
|
250
|
+
readPeerCredentials(client->getFd(), &uid, &gid);
|
251
|
+
if (server->authorizeByUid(uid)) {
|
252
|
+
SKC_INFO_FROM_STATIC(server, client, "Authenticated with UID: " << uid);
|
253
|
+
auth.uid = uid;
|
254
|
+
auth.canReadPool = true;
|
255
|
+
auth.canModifyPool = true;
|
256
|
+
auth.canInspectState = auth.canInspectState || uid == 0 || uid == geteuid();
|
257
|
+
auth.canAdminister = auth.canAdminister || uid == 0 || uid == geteuid();
|
258
|
+
} else {
|
259
|
+
SKC_INFO_FROM_STATIC(server, client, "Authentication failed for UID: " << uid);
|
260
|
+
}
|
261
|
+
} catch (const SystemException &e) {
|
262
|
+
if (e.code() != ENOSYS && e.code() != EPROTONOSUPPORT) {
|
263
|
+
throw;
|
264
|
+
}
|
265
|
+
}
|
266
|
+
|
267
|
+
if (server->apiAccountDatabase->empty()) {
|
268
|
+
SKC_INFO_FROM_STATIC(server, client,
|
269
|
+
"Authenticated as administrator because API account database is empty");
|
270
|
+
auth.apiKey = ApplicationPool2::ApiKey::makeSuper();
|
271
|
+
auth.canReadPool = true;
|
272
|
+
auth.canModifyPool = true;
|
273
|
+
auth.canInspectState = true;
|
274
|
+
auth.canAdminister = true;
|
275
|
+
} else if (parseBasicAuthHeader(req, username, password)) {
|
276
|
+
SKC_DEBUG_FROM_STATIC(server, client,
|
277
|
+
"HTTP basic authentication supplied: " << username);
|
278
|
+
if (username == "api") {
|
279
|
+
auth.apiKey = ApplicationPool2::ApiKey(password);
|
280
|
+
if (server->authorizeByApiKey(auth.apiKey)) {
|
281
|
+
SKC_INFO_FROM_STATIC(server, client,
|
282
|
+
"Authenticated with API key: " << truncateApiKey(password));
|
283
|
+
assert(!auth.apiKey.isSuper());
|
284
|
+
auth.canReadPool = true;
|
285
|
+
auth.canModifyPool = true;
|
286
|
+
}
|
287
|
+
} else {
|
288
|
+
const ApiAccount *account = server->apiAccountDatabase->lookup(username);
|
289
|
+
if (account != NULL && constantTimeCompare(password, account->password)) {
|
290
|
+
SKC_INFO_FROM_STATIC(server, client,
|
291
|
+
"Authenticated with administrator account: " << username);
|
292
|
+
auth.apiKey = ApplicationPool2::ApiKey::makeSuper();
|
293
|
+
auth.canReadPool = true;
|
294
|
+
auth.canModifyPool = auth.canModifyPool || !account->readonly;
|
295
|
+
auth.canInspectState = true;
|
296
|
+
auth.canAdminister = auth.canAdminister || !account->readonly;
|
297
|
+
}
|
298
|
+
}
|
299
|
+
}
|
300
|
+
|
301
|
+
return auth;
|
302
|
+
}
|
303
|
+
|
304
|
+
template<typename ApiServer, typename Client, typename Request>
|
305
|
+
inline bool
|
306
|
+
authorizeStateInspectionOperation(ApiServer *server, Client *client, Request *req) {
|
307
|
+
return authorize(server, client, req).canInspectState;
|
308
|
+
}
|
309
|
+
|
310
|
+
template<typename ApiServer, typename Client, typename Request>
|
311
|
+
inline bool
|
312
|
+
authorizeAdminOperation(ApiServer *server, Client *client, Request *req) {
|
313
|
+
return authorize(server, client, req).canAdminister;
|
314
|
+
}
|
315
|
+
|
316
|
+
|
317
|
+
/*******************************
|
318
|
+
*
|
319
|
+
* Utility functions
|
320
|
+
*
|
321
|
+
*******************************/
|
322
|
+
|
323
|
+
inline VariantMap
|
324
|
+
parseQueryString(const StaticString &query) {
|
325
|
+
VariantMap params;
|
326
|
+
const char *pos = query.data();
|
327
|
+
const char *end = query.data() + query.size();
|
328
|
+
|
329
|
+
while (pos < end) {
|
330
|
+
const char *assignmentPos = (const char *) memchr(pos, '=', end - pos);
|
331
|
+
if (assignmentPos != NULL) {
|
332
|
+
string name = urldecode(StaticString(pos, assignmentPos - pos));
|
333
|
+
const char *sepPos = (const char *) memchr(assignmentPos + 1, '&',
|
334
|
+
end - assignmentPos - 1);
|
335
|
+
if (sepPos != NULL) {
|
336
|
+
string value = urldecode(StaticString(assignmentPos + 1,
|
337
|
+
sepPos - assignmentPos - 1));
|
338
|
+
params.set(name, value);
|
339
|
+
pos = sepPos + 1;
|
340
|
+
} else {
|
341
|
+
StaticString value(assignmentPos + 1, end - assignmentPos - 1);
|
342
|
+
params.set(name, value);
|
343
|
+
pos = end;
|
344
|
+
}
|
345
|
+
} else {
|
346
|
+
throw SyntaxError("Invalid query string format");
|
347
|
+
}
|
348
|
+
}
|
349
|
+
|
350
|
+
return params;
|
351
|
+
}
|
352
|
+
|
353
|
+
inline string
|
354
|
+
truncateApiKey(const StaticString &apiKey) {
|
355
|
+
assert(apiKey.size() == ApplicationPool2::ApiKey::SIZE);
|
356
|
+
char prefix[3];
|
357
|
+
memcpy(prefix, apiKey.data(), 3);
|
358
|
+
return string(prefix, 3) + "*****";
|
359
|
+
}
|
360
|
+
|
361
|
+
template<typename Server, typename Client, typename Request>
|
362
|
+
struct ApiServerInternalHttpResponse {
|
363
|
+
static const int ERROR_INVALID_HEADER = -1;
|
364
|
+
static const int ERROR_INVALID_BODY = -2;
|
365
|
+
static const int ERROR_INTERNAL = -3;
|
366
|
+
|
367
|
+
Server *server;
|
368
|
+
Client *client;
|
369
|
+
Request *req;
|
370
|
+
int status;
|
371
|
+
StringKeyTable<string> headers;
|
372
|
+
string body;
|
373
|
+
|
374
|
+
vector<string> debugLogs;
|
375
|
+
string errorLogs;
|
376
|
+
BufferedIO io;
|
377
|
+
};
|
378
|
+
|
379
|
+
template<typename Server, typename Client, typename Request>
|
380
|
+
struct ApiServerInternalHttpRequest {
|
381
|
+
Server *server;
|
382
|
+
Client *client;
|
383
|
+
Request *req;
|
384
|
+
|
385
|
+
string address;
|
386
|
+
http_method method;
|
387
|
+
string uri;
|
388
|
+
StringKeyTable<string> headers;
|
389
|
+
boost::function<void (ApiServerInternalHttpResponse<Server, Client, Request>)> callback;
|
390
|
+
|
391
|
+
unsigned long long timeout;
|
392
|
+
boost::function<
|
393
|
+
void (ApiServerInternalHttpRequest<Server, Client, Request> &req,
|
394
|
+
ApiServerInternalHttpResponse<Server, Client, Request> &resp,
|
395
|
+
BufferedIO &io
|
396
|
+
)> bodyProcessor;
|
397
|
+
|
398
|
+
ApiServerInternalHttpRequest()
|
399
|
+
: server(NULL),
|
400
|
+
client(NULL),
|
401
|
+
req(NULL),
|
402
|
+
method(HTTP_GET),
|
403
|
+
timeout(60 * 1000000)
|
404
|
+
{ }
|
405
|
+
};
|
406
|
+
|
407
|
+
template<typename Server, typename Client, typename Request>
|
408
|
+
inline void
|
409
|
+
apiServerMakeInternalHttpRequestCallbackWrapper(
|
410
|
+
boost::function<void (ApiServerInternalHttpResponse<Server, Client, Request>)> callback,
|
411
|
+
ApiServerInternalHttpResponse<Server, Client, Request> resp)
|
412
|
+
{
|
413
|
+
if (!resp.debugLogs.empty()) {
|
414
|
+
foreach (string log, resp.debugLogs) {
|
415
|
+
SKC_DEBUG_FROM_STATIC(resp.server, resp.client, log);
|
416
|
+
}
|
417
|
+
}
|
418
|
+
if (!resp.errorLogs.empty()) {
|
419
|
+
SKC_ERROR_FROM_STATIC(resp.server, resp.client, resp.errorLogs);
|
420
|
+
}
|
421
|
+
callback(resp);
|
422
|
+
resp.server->unrefRequest(resp.req, __FILE__, __LINE__);
|
423
|
+
}
|
424
|
+
|
425
|
+
template<typename Server, typename Client, typename Request>
|
426
|
+
inline void
|
427
|
+
apiServerMakeInternalHttpRequestThreadMain(ApiServerInternalHttpRequest<Server, Client, Request> req) {
|
428
|
+
typedef ApiServerInternalHttpRequest<Server, Client, Request> InternalRequest;
|
429
|
+
typedef ApiServerInternalHttpResponse<Server, Client, Request> InternalResponse;
|
430
|
+
|
431
|
+
struct Guard {
|
432
|
+
InternalRequest &req;
|
433
|
+
InternalResponse &resp;
|
434
|
+
SafeLibevPtr libev;
|
435
|
+
bool cleared;
|
436
|
+
|
437
|
+
Guard(InternalRequest &_req, InternalResponse &_resp, const SafeLibevPtr &_libev)
|
438
|
+
: req(_req),
|
439
|
+
resp(_resp),
|
440
|
+
libev(_libev),
|
441
|
+
cleared(false)
|
442
|
+
{ }
|
443
|
+
|
444
|
+
~Guard() {
|
445
|
+
if (!cleared) {
|
446
|
+
resp.status = InternalResponse::ERROR_INTERNAL;
|
447
|
+
resp.headers.clear();
|
448
|
+
resp.body.clear();
|
449
|
+
libev->runLater(boost::bind(
|
450
|
+
apiServerMakeInternalHttpRequestCallbackWrapper<Server, Client, Request>,
|
451
|
+
req.callback, resp));
|
452
|
+
}
|
453
|
+
}
|
454
|
+
|
455
|
+
void clear() {
|
456
|
+
cleared = true;
|
457
|
+
}
|
458
|
+
};
|
459
|
+
|
460
|
+
InternalResponse resp;
|
461
|
+
SafeLibevPtr libev = req.server->getContext()->libev;
|
462
|
+
|
463
|
+
resp.server = req.server;
|
464
|
+
resp.client = req.client;
|
465
|
+
resp.req = req.req;
|
466
|
+
resp.status = InternalResponse::ERROR_INTERNAL;
|
467
|
+
|
468
|
+
Guard guard(req, resp, libev);
|
469
|
+
|
470
|
+
|
471
|
+
try {
|
472
|
+
FileDescriptor conn(connectToServer(req.address, NULL, 0), __FILE__, __LINE__);
|
473
|
+
BufferedIO io(conn);
|
474
|
+
|
475
|
+
string header;
|
476
|
+
header.append(http_method_str(req.method));
|
477
|
+
header.append(" ", 1);
|
478
|
+
header.append(req.uri);
|
479
|
+
header.append(" HTTP/1.1\r\n");
|
480
|
+
|
481
|
+
StringKeyTable<string>::ConstIterator it(req.headers);
|
482
|
+
while (*it != NULL) {
|
483
|
+
header.append(it.getKey());
|
484
|
+
header.append(": ", 2);
|
485
|
+
header.append(it.getValue());
|
486
|
+
header.append("\r\n", 2);
|
487
|
+
it.next();
|
488
|
+
}
|
489
|
+
header.append("Connection: close\r\n\r\n");
|
490
|
+
|
491
|
+
writeExact(conn, header, &req.timeout);
|
492
|
+
|
493
|
+
|
494
|
+
string statusLine = io.readLine();
|
495
|
+
resp.debugLogs.push_back("Internal request response data: \"" + cEscapeString(statusLine) + "\"");
|
496
|
+
boost::regex statusLineRegex("^HTTP/.*? ([0-9]+) (.*)$");
|
497
|
+
boost::smatch results;
|
498
|
+
if (!boost::regex_match(statusLine, results, statusLineRegex)) {
|
499
|
+
guard.clear();
|
500
|
+
resp.status = InternalResponse::ERROR_INVALID_HEADER;
|
501
|
+
libev->runLater(boost::bind(
|
502
|
+
apiServerMakeInternalHttpRequestCallbackWrapper<Server, Client, Request>,
|
503
|
+
req.callback, resp));
|
504
|
+
return;
|
505
|
+
}
|
506
|
+
resp.status = (int) stringToUint(results.str(1));
|
507
|
+
if (resp.status <= 0 || resp.status >= 1000) {
|
508
|
+
guard.clear();
|
509
|
+
resp.status = InternalResponse::ERROR_INVALID_HEADER;
|
510
|
+
libev->runLater(boost::bind(
|
511
|
+
apiServerMakeInternalHttpRequestCallbackWrapper<Server, Client, Request>,
|
512
|
+
req.callback, resp));
|
513
|
+
return;
|
514
|
+
}
|
515
|
+
|
516
|
+
string response;
|
517
|
+
while (true) {
|
518
|
+
response = io.readLine();
|
519
|
+
resp.debugLogs.push_back("Internal request response data: \"" + cEscapeString(response) + "\"");
|
520
|
+
if (response.empty()) {
|
521
|
+
guard.clear();
|
522
|
+
resp.status = InternalResponse::ERROR_INVALID_HEADER;
|
523
|
+
libev->runLater(boost::bind(
|
524
|
+
apiServerMakeInternalHttpRequestCallbackWrapper<Server, Client, Request>,
|
525
|
+
req.callback, resp));
|
526
|
+
return;
|
527
|
+
} else if (response == "\r\n") {
|
528
|
+
break;
|
529
|
+
} else {
|
530
|
+
const char *pos = (const char *) memchr(response.data(), ':', response.size());
|
531
|
+
if (pos == NULL) {
|
532
|
+
guard.clear();
|
533
|
+
resp.status = InternalResponse::ERROR_INVALID_HEADER;
|
534
|
+
libev->runLater(boost::bind(
|
535
|
+
apiServerMakeInternalHttpRequestCallbackWrapper<Server, Client, Request>,
|
536
|
+
req.callback, resp));
|
537
|
+
return;
|
538
|
+
}
|
539
|
+
|
540
|
+
string key(strip(response.substr(0, pos - response.data())));
|
541
|
+
string value(response.substr(pos - response.data()));
|
542
|
+
value.erase(0, 1);
|
543
|
+
value = strip(value);
|
544
|
+
if (!value.empty() && value[value.size() - 1] == '\r') {
|
545
|
+
value.erase(value.size() - 1, 1);
|
546
|
+
}
|
547
|
+
|
548
|
+
if (key.empty() || value.empty()) {
|
549
|
+
guard.clear();
|
550
|
+
resp.status = InternalResponse::ERROR_INVALID_HEADER;
|
551
|
+
libev->runLater(boost::bind(
|
552
|
+
apiServerMakeInternalHttpRequestCallbackWrapper<Server, Client, Request>,
|
553
|
+
req.callback, resp));
|
554
|
+
return;
|
555
|
+
}
|
556
|
+
|
557
|
+
resp.headers.insert(key, value);
|
558
|
+
}
|
559
|
+
}
|
560
|
+
|
561
|
+
if (req.bodyProcessor != NULL) {
|
562
|
+
req.bodyProcessor(req, resp, io);
|
563
|
+
} else {
|
564
|
+
resp.body = io.readAll(&req.timeout);
|
565
|
+
}
|
566
|
+
guard.clear();
|
567
|
+
libev->runLater(boost::bind(
|
568
|
+
apiServerMakeInternalHttpRequestCallbackWrapper<Server, Client, Request>,
|
569
|
+
req.callback, resp));
|
570
|
+
} catch (const oxt::tracable_exception &e) {
|
571
|
+
resp.errorLogs.append("Exception: ");
|
572
|
+
resp.errorLogs.append(e.what());
|
573
|
+
resp.errorLogs.append("\n");
|
574
|
+
resp.errorLogs.append(e.backtrace());
|
575
|
+
}
|
576
|
+
}
|
577
|
+
|
578
|
+
/**
|
579
|
+
* Utility function for API servers for making an internal HTTP request,
|
580
|
+
* usually to another agent. The request is made in a background thread.
|
581
|
+
* When done, the callback is called on the event loop. While the request
|
582
|
+
* is being made, a reference to the ServerKit request object is held.
|
583
|
+
*
|
584
|
+
* This is not a fully featured HTTP client and doesn't fully correctly
|
585
|
+
* parse HTTP, so it can't be used with arbitrary servers. It doesn't
|
586
|
+
* support keep-alive and chunked transfer-encodings.
|
587
|
+
*/
|
588
|
+
template<typename Server, typename Client, typename Request>
|
589
|
+
inline void
|
590
|
+
apiServerMakeInternalHttpRequest(const ApiServerInternalHttpRequest<Server, Client, Request> ¶ms) {
|
591
|
+
params.server->refRequest(params.req, __FILE__, __LINE__);
|
592
|
+
oxt::thread(boost::bind(
|
593
|
+
apiServerMakeInternalHttpRequestThreadMain<Server, Client, Request>,
|
594
|
+
params), "Internal HTTP request", 1024 * 128);
|
595
|
+
}
|
596
|
+
|
597
|
+
|
598
|
+
/*******************************
|
599
|
+
*
|
600
|
+
* Common endpoints
|
601
|
+
*
|
602
|
+
*******************************/
|
603
|
+
|
604
|
+
template<typename Server, typename Client, typename Request>
|
605
|
+
inline void
|
606
|
+
apiServerRespondWith401(Server *server, Client *client, Request *req) {
|
607
|
+
ServerKit::HeaderTable headers;
|
608
|
+
headers.insert(req->pool, "Cache-Control", "no-cache, no-store, must-revalidate");
|
609
|
+
headers.insert(req->pool, "WWW-Authenticate", "Basic realm=\"api\"");
|
610
|
+
server->writeSimpleResponse(client, 401, &headers, "Unauthorized");
|
611
|
+
if (!req->ended()) {
|
612
|
+
server->endRequest(&client, &req);
|
613
|
+
}
|
614
|
+
}
|
615
|
+
|
616
|
+
template<typename Server, typename Client, typename Request>
|
617
|
+
inline void
|
618
|
+
apiServerRespondWith404(Server *server, Client *client, Request *req) {
|
619
|
+
ServerKit::HeaderTable headers;
|
620
|
+
headers.insert(req->pool, "Cache-Control", "no-cache, no-store, must-revalidate");
|
621
|
+
server->writeSimpleResponse(client, 404, &headers, "Not found");
|
622
|
+
if (!req->ended()) {
|
623
|
+
server->endRequest(&client, &req);
|
624
|
+
}
|
625
|
+
}
|
626
|
+
|
627
|
+
template<typename Server, typename Client, typename Request>
|
628
|
+
inline void
|
629
|
+
apiServerRespondWith405(Server *server, Client *client, Request *req) {
|
630
|
+
ServerKit::HeaderTable headers;
|
631
|
+
headers.insert(req->pool, "Cache-Control", "no-cache, no-store, must-revalidate");
|
632
|
+
server->writeSimpleResponse(client, 405, &headers, "Method not allowed");
|
633
|
+
if (!req->ended()) {
|
634
|
+
server->endRequest(&client, &req);
|
635
|
+
}
|
636
|
+
}
|
637
|
+
|
638
|
+
template<typename Server, typename Client, typename Request>
|
639
|
+
inline void
|
640
|
+
apiServerRespondWith413(Server *server, Client *client, Request *req) {
|
641
|
+
ServerKit::HeaderTable headers;
|
642
|
+
headers.insert(req->pool, "Cache-Control", "no-cache, no-store, must-revalidate");
|
643
|
+
server->writeSimpleResponse(client, 413, &headers, "Request body too large");
|
644
|
+
if (!req->ended()) {
|
645
|
+
server->endRequest(&client, &req);
|
646
|
+
}
|
647
|
+
}
|
648
|
+
|
649
|
+
template<typename Server, typename Client, typename Request>
|
650
|
+
inline void
|
651
|
+
apiServerRespondWith422(Server *server, Client *client, Request *req, const StaticString &body) {
|
652
|
+
ServerKit::HeaderTable headers;
|
653
|
+
headers.insert(req->pool, "Cache-Control", "no-cache, no-store, must-revalidate");
|
654
|
+
headers.insert(req->pool, "Content-Type", "text/plain; charset=utf-8");
|
655
|
+
server->writeSimpleResponse(client, 422, &headers, body);
|
656
|
+
if (!req->ended()) {
|
657
|
+
server->endRequest(&client, &req);
|
658
|
+
}
|
659
|
+
}
|
660
|
+
|
661
|
+
template<typename Server, typename Client, typename Request>
|
662
|
+
inline void
|
663
|
+
apiServerRespondWith500(Server *server, Client *client, Request *req, const StaticString &body) {
|
664
|
+
ServerKit::HeaderTable headers;
|
665
|
+
headers.insert(req->pool, "Cache-Control", "no-cache, no-store, must-revalidate");
|
666
|
+
headers.insert(req->pool, "Content-Type", "text/plain; charset=utf-8");
|
667
|
+
server->writeSimpleResponse(client, 500, &headers, body);
|
668
|
+
if (!req->ended()) {
|
669
|
+
server->endRequest(&client, &req);
|
670
|
+
}
|
671
|
+
}
|
672
|
+
|
673
|
+
template<typename Server, typename Client, typename Request>
|
674
|
+
inline void
|
675
|
+
apiServerProcessPing(Server *server, Client *client, Request *req) {
|
676
|
+
Authorization auth(authorize(server, client, req));
|
677
|
+
if (auth.canReadPool || auth.canInspectState) {
|
678
|
+
ServerKit::HeaderTable headers;
|
679
|
+
headers.insert(req->pool, "Content-Type", "application/json");
|
680
|
+
server->writeSimpleResponse(client, 200, &headers, "{ \"status\": \"ok\" }");
|
681
|
+
if (!req->ended()) {
|
682
|
+
server->endRequest(&client, &req);
|
683
|
+
}
|
684
|
+
} else {
|
685
|
+
apiServerRespondWith401(server, client, req);
|
686
|
+
}
|
687
|
+
}
|
688
|
+
|
689
|
+
template<typename Server, typename Client, typename Request>
|
690
|
+
inline void
|
691
|
+
apiServerProcessVersion(Server *server, Client *client, Request *req) {
|
692
|
+
Authorization auth(authorize(server, client, req));
|
693
|
+
if (auth.canReadPool || auth.canInspectState) {
|
694
|
+
ServerKit::HeaderTable headers;
|
695
|
+
headers.insert(req->pool, "Content-Type", "application/json");
|
696
|
+
|
697
|
+
Json::Value response;
|
698
|
+
response["program_name"] = PROGRAM_NAME;
|
699
|
+
response["program_version"] = PASSENGER_VERSION;
|
700
|
+
response["api_version"] = PASSENGER_API_VERSION;
|
701
|
+
response["api_version_major"] = PASSENGER_API_VERSION_MAJOR;
|
702
|
+
response["api_version_minor"] = PASSENGER_API_VERSION_MINOR;
|
703
|
+
#ifdef PASSENGER_IS_ENTERPRISE
|
704
|
+
response["passenger_enterprise"] = true;
|
705
|
+
#endif
|
706
|
+
|
707
|
+
server->writeSimpleResponse(client, 200, &headers,
|
708
|
+
response.toStyledString());
|
709
|
+
if (!req->ended()) {
|
710
|
+
server->endRequest(&client, &req);
|
711
|
+
}
|
712
|
+
} else {
|
713
|
+
apiServerRespondWith401(server, client, req);
|
714
|
+
}
|
715
|
+
}
|
716
|
+
|
717
|
+
template<typename Server, typename Client, typename Request>
|
718
|
+
inline void
|
719
|
+
apiServerProcessBacktraces(Server *server, Client *client, Request *req) {
|
720
|
+
if (authorizeStateInspectionOperation(server, client, req)) {
|
721
|
+
ServerKit::HeaderTable headers;
|
722
|
+
headers.insert(req->pool, "Content-Type", "text/plain");
|
723
|
+
server->writeSimpleResponse(client, 200, &headers,
|
724
|
+
psg_pstrdup(req->pool, oxt::thread::all_backtraces()));
|
725
|
+
if (!req->ended()) {
|
726
|
+
server->endRequest(&client, &req);
|
727
|
+
}
|
728
|
+
} else {
|
729
|
+
apiServerRespondWith401(server, client, req);
|
730
|
+
}
|
731
|
+
}
|
732
|
+
|
733
|
+
template<typename Server, typename Client, typename Request>
|
734
|
+
inline void
|
735
|
+
apiServerProcessShutdown(Server *server, Client *client, Request *req) {
|
736
|
+
if (req->method != HTTP_POST) {
|
737
|
+
apiServerRespondWith405(server, client, req);
|
738
|
+
} else if (authorizeAdminOperation(server, client, req)) {
|
739
|
+
ServerKit::HeaderTable headers;
|
740
|
+
headers.insert(req->pool, "Content-Type", "application/json");
|
741
|
+
server->exitEvent->notify();
|
742
|
+
server->writeSimpleResponse(client, 200, &headers, "{ \"status\": \"ok\" }");
|
743
|
+
if (!req->ended()) {
|
744
|
+
server->endRequest(&client, &req);
|
745
|
+
}
|
746
|
+
} else {
|
747
|
+
apiServerRespondWith401(server, client, req);
|
748
|
+
}
|
749
|
+
}
|
750
|
+
|
751
|
+
template<typename Server, typename Client, typename Request>
|
752
|
+
inline void
|
753
|
+
apiServerProcessReopenLogs(Server *server, Client *client, Request *req) {
|
754
|
+
if (req->method != HTTP_POST) {
|
755
|
+
apiServerRespondWith405(server, client, req);
|
756
|
+
} else if (authorizeAdminOperation(server, client, req)) {
|
757
|
+
int e;
|
758
|
+
ServerKit::HeaderTable headers;
|
759
|
+
headers.insert(req->pool, "Content-Type", "application/json");
|
760
|
+
|
761
|
+
string logFile = getLogFile();
|
762
|
+
if (logFile.empty()) {
|
763
|
+
server->writeSimpleResponse(client, 500, &headers, "{ \"status\": \"error\", "
|
764
|
+
"\"code\": \"NO_LOG_FILE\", "
|
765
|
+
"\"message\": \"" PROGRAM_NAME " was not configured with a log file.\" }\n");
|
766
|
+
if (!req->ended()) {
|
767
|
+
server->endRequest(&client, &req);
|
768
|
+
}
|
769
|
+
return;
|
770
|
+
}
|
771
|
+
|
772
|
+
if (!setLogFile(logFile, &e)) {
|
773
|
+
unsigned int bufsize = 1024;
|
774
|
+
char *message = (char *) psg_pnalloc(req->pool, bufsize);
|
775
|
+
snprintf(message, bufsize, "{ \"status\": \"error\", "
|
776
|
+
"\"code\": \"LOG_FILE_OPEN_ERROR\", "
|
777
|
+
"\"message\": \"Cannot reopen log file %s: %s (errno=%d)\" }",
|
778
|
+
logFile.c_str(), strerror(e), e);
|
779
|
+
server->writeSimpleResponse(client, 500, &headers, message);
|
780
|
+
if (!req->ended()) {
|
781
|
+
server->endRequest(&client, &req);
|
782
|
+
}
|
783
|
+
return;
|
784
|
+
}
|
785
|
+
P_NOTICE("Log file reopened.");
|
786
|
+
|
787
|
+
if (hasFileDescriptorLogFile()) {
|
788
|
+
if (!setFileDescriptorLogFile(getFileDescriptorLogFile(), &e)) {
|
789
|
+
unsigned int bufsize = 1024;
|
790
|
+
char *message = (char *) psg_pnalloc(req->pool, bufsize);
|
791
|
+
snprintf(message, bufsize, "{ \"status\": \"error\", "
|
792
|
+
"\"code\": \"FD_LOG_FILE_OPEN_ERROR\", "
|
793
|
+
"\"message\": \"Cannot reopen file descriptor log file %s: %s (errno=%d)\" }",
|
794
|
+
getFileDescriptorLogFile().c_str(), strerror(e), e);
|
795
|
+
server->writeSimpleResponse(client, 500, &headers, message);
|
796
|
+
if (!req->ended()) {
|
797
|
+
server->endRequest(&client, &req);
|
798
|
+
}
|
799
|
+
return;
|
800
|
+
}
|
801
|
+
P_NOTICE("File descriptor log file reopened.");
|
802
|
+
}
|
803
|
+
|
804
|
+
server->writeSimpleResponse(client, 200, &headers, "{ \"status\": \"ok\" }\n");
|
805
|
+
|
806
|
+
if (!req->ended()) {
|
807
|
+
server->endRequest(&client, &req);
|
808
|
+
}
|
809
|
+
} else {
|
810
|
+
apiServerRespondWith401(server, client, req);
|
811
|
+
}
|
812
|
+
}
|
813
|
+
|
814
|
+
template<typename Server, typename Client, typename Request>
|
815
|
+
inline void
|
816
|
+
_apiServerProcessReinheritLogsResponseBody(
|
817
|
+
ApiServerInternalHttpRequest<Server, Client, Request> &req,
|
818
|
+
ApiServerInternalHttpResponse<Server, Client, Request> &resp,
|
819
|
+
BufferedIO &io)
|
820
|
+
{
|
821
|
+
typedef ApiServerInternalHttpResponse<Server, Client, Request> InternalResponse;
|
822
|
+
|
823
|
+
string logFilePath = resp.headers.lookupCopy("Filename");
|
824
|
+
if (logFilePath.empty()) {
|
825
|
+
resp.status = InternalResponse::ERROR_INVALID_BODY;
|
826
|
+
resp.errorLogs.append("Error communicating with Watchdog process: "
|
827
|
+
"no log filename received in response");
|
828
|
+
return;
|
829
|
+
}
|
830
|
+
|
831
|
+
int fd = readFileDescriptorWithNegotiation(io.getFd(), &req.timeout);
|
832
|
+
setLogFileWithFd(logFilePath, fd);
|
833
|
+
safelyClose(fd);
|
834
|
+
}
|
835
|
+
|
836
|
+
template<typename Server, typename Client, typename Request>
|
837
|
+
inline void
|
838
|
+
_apiServerProcessReinheritLogsDone(ApiServerInternalHttpResponse<Server, Client, Request> resp) {
|
839
|
+
typedef ApiServerInternalHttpResponse<Server, Client, Request> InternalResponse;
|
840
|
+
|
841
|
+
Server *server = resp.server;
|
842
|
+
Client *client = resp.client;
|
843
|
+
Request *req = resp.req;
|
844
|
+
int status;
|
845
|
+
StaticString body;
|
846
|
+
|
847
|
+
if (req->ended()) {
|
848
|
+
return;
|
849
|
+
}
|
850
|
+
|
851
|
+
if (resp.status < 0) {
|
852
|
+
status = 500;
|
853
|
+
switch (resp.status) {
|
854
|
+
case InternalResponse::ERROR_INVALID_HEADER:
|
855
|
+
body = "{ \"status\": \"error\", "
|
856
|
+
"\"code\": \"INHERIT_ERROR\", "
|
857
|
+
"\"message\": \"Error communicating with Watchdog process: "
|
858
|
+
"invalid response headers from Watchdog\" }\n";
|
859
|
+
break;
|
860
|
+
case InternalResponse::ERROR_INVALID_BODY:
|
861
|
+
body = "{ \"status\": \"error\", "
|
862
|
+
"\"code\": \"INHERIT_ERROR\", "
|
863
|
+
"\"message\": \"Error communicating with Watchdog process: "
|
864
|
+
"invalid response body from Watchdog\" }\n";
|
865
|
+
break;
|
866
|
+
case InternalResponse::ERROR_INTERNAL:
|
867
|
+
body = "{ \"status\": \"error\", "
|
868
|
+
"\"code\": \"INHERIT_ERROR\", "
|
869
|
+
"\"message\": \"Error communicating with Watchdog process: "
|
870
|
+
"an internal error occurred\" }\n";
|
871
|
+
break;
|
872
|
+
default:
|
873
|
+
body = "{ \"status\": \"error\", "
|
874
|
+
"\"code\": \"INHERIT_ERROR\", "
|
875
|
+
"\"message\": \"Error communicating with Watchdog process: "
|
876
|
+
"unknown error\" }\n";
|
877
|
+
break;
|
878
|
+
}
|
879
|
+
} else if (resp.status == 200) {
|
880
|
+
status = 200;
|
881
|
+
body = "{ \"status\": \"ok\" }\n";
|
882
|
+
} else {
|
883
|
+
status = 500;
|
884
|
+
body = "{ \"status\": \"error\", "
|
885
|
+
"\"code\": \"INHERIT_ERROR\", "
|
886
|
+
"\"message\": \"Error communicating with Watchdog process: non-200 response\" }\n";
|
887
|
+
}
|
888
|
+
|
889
|
+
ServerKit::HeaderTable headers;
|
890
|
+
headers.insert(req->pool, "Cache-Control", "no-cache, no-store, must-revalidate");
|
891
|
+
headers.insert(req->pool, "Content-Type", "application/json");
|
892
|
+
req->wantKeepAlive = false;
|
893
|
+
server->writeSimpleResponse(client, status, &headers, body);
|
894
|
+
if (!req->ended()) {
|
895
|
+
server->endRequest(&client, &req);
|
896
|
+
}
|
897
|
+
}
|
898
|
+
|
899
|
+
template<typename Server, typename Client, typename Request>
|
900
|
+
inline void
|
901
|
+
apiServerProcessReinheritLogs(Server *server, Client *client, Request *req,
|
902
|
+
const StaticString &instanceDir, const StaticString &fdPassingPassword)
|
903
|
+
{
|
904
|
+
if (req->method != HTTP_POST) {
|
905
|
+
apiServerRespondWith405(server, client, req);
|
906
|
+
} else if (authorizeAdminOperation(server, client, req)) {
|
907
|
+
ServerKit::HeaderTable headers;
|
908
|
+
headers.insert(req->pool, "Cache-Control", "no-cache, no-store, must-revalidate");
|
909
|
+
headers.insert(req->pool, "Content-Type", "application/json");
|
910
|
+
|
911
|
+
if (instanceDir.empty() || fdPassingPassword.empty()) {
|
912
|
+
server->writeSimpleResponse(client, 501, &headers,
|
913
|
+
"{ \"status\": \"error\", "
|
914
|
+
"\"code\": \"NO_WATCHDOG\", "
|
915
|
+
"\"message\": \"No Watchdog process\" }\n");
|
916
|
+
if (!req->ended()) {
|
917
|
+
server->endRequest(&client, &req);
|
918
|
+
}
|
919
|
+
return;
|
920
|
+
}
|
921
|
+
|
922
|
+
ApiServerInternalHttpRequest<Server, Client, Request> params;
|
923
|
+
params.server = server;
|
924
|
+
params.client = client;
|
925
|
+
params.req = req;
|
926
|
+
params.address = "unix:" + instanceDir + "/agents.s/watchdog_api";
|
927
|
+
params.method = HTTP_GET;
|
928
|
+
params.uri = "/config/log_file.fd";
|
929
|
+
params.headers.insert("Fd-Passing-Password", fdPassingPassword);
|
930
|
+
params.callback = _apiServerProcessReinheritLogsDone<Server, Client, Request>;
|
931
|
+
params.bodyProcessor = _apiServerProcessReinheritLogsResponseBody<Server, Client, Request>;
|
932
|
+
apiServerMakeInternalHttpRequest(params);
|
933
|
+
} else {
|
934
|
+
apiServerRespondWith401(server, client, req);
|
935
|
+
}
|
936
|
+
}
|
937
|
+
|
938
|
+
|
939
|
+
} // namespace Passenger
|
940
|
+
|
941
|
+
#endif /* _PASSENGER_API_SERVER_UTILS_H_ */
|