diff options
-rw-r--r-- | .gitignore | 8 | ||||
-rw-r--r-- | Makefile | 13 | ||||
-rw-r--r-- | include/config.h | 9 | ||||
-rw-r--r-- | include/exchange_update_exception.h | 10 | ||||
-rw-r--r-- | include/qalculate_exception.h | 15 | ||||
-rw-r--r-- | include/security_util.h | 9 | ||||
-rw-r--r-- | include/timeout_exception.h | 10 | ||||
-rw-r--r-- | qalculate-helper.cpp | 232 | ||||
-rw-r--r-- | src/exchange_update_exception.cpp | 7 | ||||
-rw-r--r-- | src/qalculate-helper.cpp | 228 | ||||
-rw-r--r-- | src/qalculate_exception.cpp | 12 | ||||
-rw-r--r-- | src/security_util.cpp | 82 | ||||
-rw-r--r-- | src/timeout_exception.cpp | 7 |
13 files changed, 406 insertions, 236 deletions
@@ -1,5 +1,13 @@ | |||
1 | # IDE | ||
2 | /.project | ||
3 | /.cproject | ||
4 | /.vscode/ | ||
5 | /.settings/ | ||
6 | |||
1 | # environment | 7 | # environment |
2 | /.build_env | 8 | /.build_env |
3 | 9 | ||
4 | # binary | 10 | # binary |
5 | /qalculate-helper | 11 | /qalculate-helper |
12 | /target/ | ||
13 | /.debug/ | ||
@@ -2,8 +2,13 @@ include .build_env | |||
2 | export | 2 | export |
3 | 3 | ||
4 | NAME='qalculate-helper' | 4 | NAME='qalculate-helper' |
5 | FLAGS+=-I${QALCULATE_INCLUDE_PATH} | 5 | FLAGS+=-Iinclude/ |
6 | FLAGS+=-L${QALCULATE_LIBRARY_PATH} | 6 | ifneq ($(origin QALCULATE_INCLUDE_PATH), undefined) |
7 | FLAGS+=-I${QALCULATE_INCLUDE_PATH} | ||
8 | endif | ||
9 | ifneq ($(origin QALCULATE_LIBRARY_PATH), undefined) | ||
10 | FLAGS+=-L${QALCULATE_LIBRARY_PATH} | ||
11 | endif | ||
7 | FLAGS+=-Wl,-Bstatic | 12 | FLAGS+=-Wl,-Bstatic |
8 | FLAGS+=-lqalculate | 13 | FLAGS+=-lqalculate |
9 | FLAGS+=-Wl,-Bdynamic | 14 | FLAGS+=-Wl,-Bdynamic |
@@ -24,7 +29,7 @@ FLAGS+=-ansi | |||
24 | FLAGS+=-O3 | 29 | FLAGS+=-O3 |
25 | FLAGS+=-D_FORTIFY_SOURCE=2 | 30 | FLAGS+=-D_FORTIFY_SOURCE=2 |
26 | FLAGS+=-fPIE | 31 | FLAGS+=-fPIE |
27 | FLAGS+=-std=c++2a | 32 | FLAGS+=-std=c++17 |
28 | FLAGS+=-o $(NAME) | 33 | FLAGS+=-o $(NAME) |
29 | FLAGS+=-march=native | 34 | FLAGS+=-march=native |
30 | ifneq ($(origin SETUID), undefined) | 35 | ifneq ($(origin SETUID), undefined) |
@@ -35,5 +40,5 @@ ifneq ($(origin SECCOMP), undefined) | |||
35 | endif | 40 | endif |
36 | 41 | ||
37 | all: | 42 | all: |
38 | g++ qalculate-helper.cpp $(FLAGS) | 43 | g++ src/security_util.cpp src/exchange_update_exception.cpp src/qalculate_exception.cpp src/qalculate-helper.cpp src/timeout_exception.cpp $(FLAGS) |
39 | strip $(NAME) | 44 | strip $(NAME) |
diff --git a/include/config.h b/include/config.h new file mode 100644 index 0000000..b624351 --- /dev/null +++ b/include/config.h | |||
@@ -0,0 +1,9 @@ | |||
1 | #pragma once | ||
2 | |||
3 | #define PRECISION_DEFAULT 20 | ||
4 | #define PRECISION_HIGH 900 | ||
5 | |||
6 | #define MIN_DECIMALS 20 | ||
7 | #define TIMEOUT_CALC 2000 | ||
8 | #define TIMEOUT_PRINT 2000 | ||
9 | #define TIMEOUT_UPDATE 30 | ||
diff --git a/include/exchange_update_exception.h b/include/exchange_update_exception.h new file mode 100644 index 0000000..4adab32 --- /dev/null +++ b/include/exchange_update_exception.h | |||
@@ -0,0 +1,10 @@ | |||
1 | //SPDX-License-Identifier: GPL-3.0 | ||
2 | #pragma once | ||
3 | |||
4 | #include <qalculate_exception.h> | ||
5 | |||
6 | class exchange_update_exception : public qalculate_exception { | ||
7 | public: | ||
8 | exchange_update_exception(); | ||
9 | }; | ||
10 | |||
diff --git a/include/qalculate_exception.h b/include/qalculate_exception.h new file mode 100644 index 0000000..c7e042b --- /dev/null +++ b/include/qalculate_exception.h | |||
@@ -0,0 +1,15 @@ | |||
1 | //SPDX-License-Identifier: GPL-3.0 | ||
2 | #pragma once | ||
3 | |||
4 | #include <stdexcept> | ||
5 | |||
6 | class qalculate_exception : public std::runtime_error { | ||
7 | protected: | ||
8 | int code; | ||
9 | |||
10 | qalculate_exception(int code); | ||
11 | |||
12 | public: | ||
13 | int getCode() const; | ||
14 | }; | ||
15 | |||
diff --git a/include/security_util.h b/include/security_util.h new file mode 100644 index 0000000..a6b24f1 --- /dev/null +++ b/include/security_util.h | |||
@@ -0,0 +1,9 @@ | |||
1 | #pragma once | ||
2 | |||
3 | #include <libqalculate/Calculator.h> | ||
4 | |||
5 | void do_setuid(); | ||
6 | |||
7 | void do_defang_calculator(Calculator *calc); | ||
8 | |||
9 | void do_seccomp(); | ||
diff --git a/include/timeout_exception.h b/include/timeout_exception.h new file mode 100644 index 0000000..de2d628 --- /dev/null +++ b/include/timeout_exception.h | |||
@@ -0,0 +1,10 @@ | |||
1 | //SPDX-License-Identifier: GPL-3.0 | ||
2 | #pragma once | ||
3 | |||
4 | #include <qalculate_exception.h> | ||
5 | |||
6 | class timeout_exception : public qalculate_exception { | ||
7 | public: | ||
8 | timeout_exception(); | ||
9 | }; | ||
10 | |||
diff --git a/qalculate-helper.cpp b/qalculate-helper.cpp deleted file mode 100644 index 4437476..0000000 --- a/qalculate-helper.cpp +++ /dev/null | |||
@@ -1,232 +0,0 @@ | |||
1 | #include <sstream> | ||
2 | #include <libqalculate/qalculate.h> | ||
3 | #ifdef UID | ||
4 | #include <grp.h> | ||
5 | #include <cap-ng.h> | ||
6 | #else | ||
7 | #warning "Not doing setuid/setgid, do not use in production!" | ||
8 | #endif | ||
9 | |||
10 | #ifdef SECCOMP | ||
11 | #include <seccomp.h> | ||
12 | #else | ||
13 | #warning "Not doing seccomp, do not use in production!" | ||
14 | #endif | ||
15 | |||
16 | #define PRECISION_DEFAULT 20 | ||
17 | #define PRECISION_HIGH 900 | ||
18 | |||
19 | #define MIN_DECIMALS 20 | ||
20 | #define TIMEOUT_CALC 2000 | ||
21 | #define TIMEOUT_PRINT 2000 | ||
22 | #define TIMEOUT_UPDATE 30 | ||
23 | |||
24 | #define TYPE_MESSAGE putchar(1) | ||
25 | #define TYPE_RESULT putchar(2) | ||
26 | |||
27 | #define RESULT_APPROXIMATION_NO putchar(1); | ||
28 | #define RESULT_APPROXIMATION_YES putchar(2); | ||
29 | |||
30 | #define LEVEL_INFO putchar(1) | ||
31 | #define LEVEL_WARNING putchar(2) | ||
32 | #define LEVEL_ERROR putchar(3) | ||
33 | #define LEVEL_UNKNOWN putchar(4) | ||
34 | #define SEPARATOR putchar(0) | ||
35 | |||
36 | #define COMMAND_UPDATE "update" | ||
37 | |||
38 | #define MODE_HIGH_PRECISION 2 | ||
39 | #define MODE_EXACT 3 | ||
40 | |||
41 | #define ENOARG 101; | ||
42 | #define ETIMEOUT 102; | ||
43 | #define ECANTFETCH 103; | ||
44 | |||
45 | using std::string; | ||
46 | using std::stringstream; | ||
47 | using std::getline; | ||
48 | using std::atoi; | ||
49 | |||
50 | int evaluate_single(EvaluationOptions *eo, MathStructure *result, int line_number, string expression) { | ||
51 | if (!CALCULATOR->calculate(result, CALCULATOR->unlocalizeExpression(expression), TIMEOUT_CALC, *eo)) | ||
52 | return ETIMEOUT; | ||
53 | |||
54 | CalculatorMessage* message; | ||
55 | while ((message = CALCULATOR->message())) { | ||
56 | TYPE_MESSAGE; | ||
57 | switch (message->type()) { | ||
58 | case MESSAGE_INFORMATION: | ||
59 | LEVEL_INFO; | ||
60 | break; | ||
61 | case MESSAGE_WARNING: | ||
62 | LEVEL_WARNING; | ||
63 | break; | ||
64 | case MESSAGE_ERROR: | ||
65 | LEVEL_ERROR; | ||
66 | break; | ||
67 | default: | ||
68 | LEVEL_UNKNOWN; | ||
69 | break; | ||
70 | } | ||
71 | printf("line %d: ", line_number); | ||
72 | fputs(message->c_message(), stdout); SEPARATOR; | ||
73 | CALCULATOR->nextMessage(); | ||
74 | } | ||
75 | return 0; | ||
76 | } | ||
77 | |||
78 | int evaluate(char *expression, char *mode, char *base) { | ||
79 | CALCULATOR->setExchangeRatesWarningEnabled(false); | ||
80 | CALCULATOR->loadExchangeRates(); | ||
81 | CALCULATOR->loadGlobalDefinitions(); | ||
82 | |||
83 | CALCULATOR->getActiveFunction("command")->destroy(); // rce | ||
84 | #ifdef HAS_PLOT | ||
85 | CALCULATOR->getActiveFunction("plot")->destroy(); // no use | ||
86 | #endif | ||
87 | CALCULATOR->getActiveVariable("uptime")->destroy(); // why would you | ||
88 | |||
89 | PrintOptions po; | ||
90 | |||
91 | po.base = atoi(base); | ||
92 | |||
93 | po.number_fraction_format = FRACTION_DECIMAL; | ||
94 | po.interval_display = INTERVAL_DISPLAY_PLUSMINUS; | ||
95 | po.use_unicode_signs = true; | ||
96 | //po.min_decimals = MIN_DECIMALS; | ||
97 | po.time_zone = TIME_ZONE_UTC; | ||
98 | po.abbreviate_names = true; | ||
99 | po.show_ending_zeroes = false; | ||
100 | //po.preserve_precision = true; | ||
101 | //po.restrict_to_parent_precision = false; | ||
102 | |||
103 | bool approximate = false; | ||
104 | po.is_approximate = &approximate; | ||
105 | CALCULATOR->setMessagePrintOptions(po); | ||
106 | |||
107 | EvaluationOptions eo; | ||
108 | |||
109 | eo.approximation = APPROXIMATION_TRY_EXACT; | ||
110 | eo.parse_options.unknowns_enabled = false; | ||
111 | eo.sync_units = false; | ||
112 | |||
113 | #ifdef SECCOMP | ||
114 | scmp_filter_ctx ctx; | ||
115 | ctx = seccomp_init(SCMP_ACT_KILL); | ||
116 | /* 0 */ seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0); | ||
117 | /* 1 */ seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0); | ||
118 | /* 9 */ seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mmap), 0); | ||
119 | /* 10 */ seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mprotect), 0); | ||
120 | /* 11 */ seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(munmap), 0); | ||
121 | /* 13 */ seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(rt_sigaction), 0); | ||
122 | /* 14 */ seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(rt_sigprocmask), 0); | ||
123 | /* 24 */ seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(sched_yield), 0); | ||
124 | /* 230 */ seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(clock_nanosleep), 0); | ||
125 | /* 231 */ seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0); | ||
126 | /* 262 */ seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(newfstatat), 0); | ||
127 | /* 273 */ seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(set_robust_list), 0); | ||
128 | /* 334 */ seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(rseq), 0); | ||
129 | /* 435 */ seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(clone3), 0); | ||
130 | int err = seccomp_load(ctx); | ||
131 | if (err) { | ||
132 | printf("couldn't seccomp: %d\n", err); | ||
133 | abort(); | ||
134 | } | ||
135 | #endif | ||
136 | |||
137 | int precision = PRECISION_DEFAULT; | ||
138 | switch (atoi(mode)) { | ||
139 | case MODE_EXACT: | ||
140 | eo.approximation = APPROXIMATION_EXACT; | ||
141 | po.number_fraction_format = FRACTION_DECIMAL_EXACT; | ||
142 | break; | ||
143 | |||
144 | case MODE_HIGH_PRECISION: | ||
145 | precision = PRECISION_HIGH; | ||
146 | po.indicate_infinite_series = false; | ||
147 | break; | ||
148 | } | ||
149 | |||
150 | CALCULATOR->setPrecision(precision); | ||
151 | |||
152 | MathStructure result; | ||
153 | stringstream expressions(expression); | ||
154 | string single_expression; | ||
155 | int line_number = 1; | ||
156 | int return_value; | ||
157 | while (getline(expressions, single_expression, '\n')) { | ||
158 | if ((return_value = evaluate_single(&eo, &result, line_number, single_expression))) | ||
159 | return return_value; | ||
160 | line_number++; | ||
161 | } | ||
162 | |||
163 | string string_result = CALCULATOR->print(result, TIMEOUT_PRINT, po, false, 1, TAG_TYPE_TERMINAL); | ||
164 | |||
165 | if (string_result.ends_with(CALCULATOR->timedOutString())) { | ||
166 | return ETIMEOUT; | ||
167 | |||
168 | } else { | ||
169 | TYPE_RESULT; | ||
170 | if (approximate) { | ||
171 | RESULT_APPROXIMATION_YES; | ||
172 | } else { | ||
173 | RESULT_APPROXIMATION_NO; | ||
174 | } | ||
175 | |||
176 | fputs(string_result.c_str(), stdout); | ||
177 | } | ||
178 | SEPARATOR; | ||
179 | return 0; | ||
180 | } | ||
181 | |||
182 | int update() { | ||
183 | if (!CALCULATOR->canFetch()) | ||
184 | return ECANTFETCH; | ||
185 | CALCULATOR->fetchExchangeRates(TIMEOUT_UPDATE); | ||
186 | // for some reason this returns false even when it's successful so I'm not going to check it | ||
187 | return 0; | ||
188 | } | ||
189 | |||
190 | int main(int argc, char** argv) { | ||
191 | #ifdef UID | ||
192 | if (setgroups(0, {})) { | ||
193 | perror("couldn't remove groups"); | ||
194 | abort(); | ||
195 | } | ||
196 | |||
197 | if (setresgid(UID, UID, UID)) { | ||
198 | perror("couldn't set gid"); | ||
199 | abort(); | ||
200 | } | ||
201 | |||
202 | if (setresuid(UID, UID, UID)) { | ||
203 | perror("couldn't set uid"); | ||
204 | abort(); | ||
205 | } | ||
206 | |||
207 | capng_clear(CAPNG_SELECT_BOTH); | ||
208 | if (capng_update(CAPNG_DROP, (capng_type_t)(CAPNG_EFFECTIVE | CAPNG_PERMITTED), CAP_SETGID)) { | ||
209 | printf("couldn't drop caps: can't select\n"); | ||
210 | abort(); | ||
211 | } | ||
212 | int err = capng_apply(CAPNG_SELECT_BOTH); | ||
213 | if (err) { | ||
214 | printf("couldn't drop caps: %d\n", err); | ||
215 | abort(); | ||
216 | } | ||
217 | #endif | ||
218 | |||
219 | if (argc < 2) | ||
220 | return ENOARG; | ||
221 | |||
222 | new Calculator(true); | ||
223 | if (argc == 2) { | ||
224 | if (strcmp(argv[1], COMMAND_UPDATE) == 0) | ||
225 | return update(); | ||
226 | else | ||
227 | return 1; | ||
228 | } else { | ||
229 | return evaluate(argv[1], argv[2], argv[3]); | ||
230 | } | ||
231 | } | ||
232 | |||
diff --git a/src/exchange_update_exception.cpp b/src/exchange_update_exception.cpp new file mode 100644 index 0000000..125ed21 --- /dev/null +++ b/src/exchange_update_exception.cpp | |||
@@ -0,0 +1,7 @@ | |||
1 | //SPDX-License-Identifier: GPL-3.0 | ||
2 | |||
3 | #include <exchange_update_exception.h> | ||
4 | |||
5 | exchange_update_exception::exchange_update_exception() : | ||
6 | qalculate_exception(103) { | ||
7 | } | ||
diff --git a/src/qalculate-helper.cpp b/src/qalculate-helper.cpp new file mode 100644 index 0000000..3356fb2 --- /dev/null +++ b/src/qalculate-helper.cpp | |||
@@ -0,0 +1,228 @@ | |||
1 | //SPDX-License-Identifier: GPL-3.0 | ||
2 | /* | ||
3 | * qalculate-helper.cpp | ||
4 | * Copyright (C) 2024 Marko Zajc | ||
5 | * | ||
6 | * This program is free software: you can redistribute it and/or modify it under the | ||
7 | * terms of the GNU General Public License as published by the Free Software | ||
8 | * Foundation, version 3. | ||
9 | * | ||
10 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY | ||
11 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A | ||
12 | * PARTICULAR PURPOSE. See the GNU General Public License for more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU General Public License along with this | ||
15 | * program. If not, see <https://www.gnu.org/licenses/>. | ||
16 | */ | ||
17 | |||
18 | #include <config.h> | ||
19 | #include <exchange_update_exception.h> | ||
20 | #include <libqalculate/Calculator.h> | ||
21 | #include <libqalculate/includes.h> | ||
22 | #include <libqalculate/MathStructure.h> | ||
23 | #include <security_util.h> | ||
24 | #include <timeout_exception.h> | ||
25 | #include <cstddef> | ||
26 | #include <cstdio> | ||
27 | #include <cstdlib> | ||
28 | #include <cstring> | ||
29 | #include <sstream> | ||
30 | #include <string> | ||
31 | #include <vector> | ||
32 | |||
33 | using std::string; | ||
34 | using std::stringstream; | ||
35 | using std::getline; | ||
36 | using std::vector; | ||
37 | using std::string_view; | ||
38 | using std::size_t; | ||
39 | |||
40 | #if __cplusplus >= 201703L | ||
41 | #include <string_view> | ||
42 | |||
43 | static bool ends_with(string_view str, string_view suffix) { | ||
44 | return str.size() >= suffix.size() && str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0; | ||
45 | } | ||
46 | |||
47 | #endif | ||
48 | |||
49 | const char TYPE_MESSAGE = 1; | ||
50 | const char TYPE_RESULT = 2; | ||
51 | |||
52 | const char RESULT_APPROXIMATION_NO = 1; | ||
53 | const char RESULT_APPROXIMATION_YES = 2; | ||
54 | |||
55 | const char LEVEL_INFO = 1; | ||
56 | const char LEVEL_WARNING = 2; | ||
57 | const char LEVEL_ERROR = 3; | ||
58 | const char LEVEL_UNKNOWN = 4; | ||
59 | const char SEPARATOR = 0; | ||
60 | |||
61 | #define COMMAND_UPDATE "update" | ||
62 | |||
63 | const unsigned long MODE_PRECISION = 1 << 0; | ||
64 | const unsigned long MODE_EXACT = 1 << 1; | ||
65 | const unsigned long MODE_NOCOLOR = 1 << 2; | ||
66 | |||
67 | const int ETIMEOUT = 102; | ||
68 | const int ECANTFETCH = 103; | ||
69 | |||
70 | static MathStructure evaluate_single(Calculator *calc, const EvaluationOptions &eo, unsigned long line_number, | ||
71 | const string &expression) { | ||
72 | MathStructure result; | ||
73 | if (!calc->calculate(&result, calc->unlocalizeExpression(expression), TIMEOUT_CALC, eo)) | ||
74 | throw timeout_exception(); | ||
75 | |||
76 | const CalculatorMessage *message; | ||
77 | while ((message = CALCULATOR->message())) { | ||
78 | putchar(TYPE_MESSAGE); | ||
79 | switch (message->type()) { | ||
80 | case MESSAGE_INFORMATION: | ||
81 | putchar(LEVEL_INFO); | ||
82 | break; | ||
83 | case MESSAGE_WARNING: | ||
84 | putchar(LEVEL_WARNING); | ||
85 | break; | ||
86 | case MESSAGE_ERROR: | ||
87 | putchar(LEVEL_ERROR); | ||
88 | break; | ||
89 | default: | ||
90 | putchar(LEVEL_UNKNOWN); | ||
91 | break; | ||
92 | } | ||
93 | printf("line %lu: ", line_number); | ||
94 | fputs(message->c_message(), stdout); | ||
95 | putchar(SEPARATOR); | ||
96 | calc->nextMessage(); | ||
97 | } | ||
98 | return result; | ||
99 | } | ||
100 | |||
101 | static bool mode_set(unsigned long mode, unsigned long test) { | ||
102 | return mode & test; | ||
103 | } | ||
104 | |||
105 | static void set_precision(Calculator *calc, unsigned long mode, EvaluationOptions &eo, PrintOptions &po) { | ||
106 | int precision = PRECISION_DEFAULT; | ||
107 | |||
108 | if (mode_set(mode, MODE_EXACT)) { | ||
109 | eo.approximation = APPROXIMATION_EXACT; | ||
110 | po.number_fraction_format = FRACTION_DECIMAL_EXACT; | ||
111 | |||
112 | } else if (mode_set(mode, MODE_PRECISION)) { | ||
113 | precision = PRECISION_HIGH; | ||
114 | po.indicate_infinite_series = false; | ||
115 | } | ||
116 | |||
117 | calc->setPrecision(precision); | ||
118 | } | ||
119 | |||
120 | MathStructure evaluate_all(const vector<string> &expressions, const EvaluationOptions &eo, Calculator *calc) { | ||
121 | for (size_t i = 0; i < expressions.size() - 1; ++i) | ||
122 | evaluate_single(calc, eo, i + 1, expressions[i]); | ||
123 | return evaluate_single(calc, eo, expressions.size(), expressions.back()); | ||
124 | } | ||
125 | |||
126 | void print_result(Calculator *calc, const MathStructure &result_struct, const PrintOptions &po, int mode, | ||
127 | bool approximate) { | ||
128 | string result = calc->print(result_struct, TIMEOUT_PRINT, po, false, mode_set(mode, MODE_NOCOLOR) ? 0 : 1, | ||
129 | TAG_TYPE_TERMINAL); | ||
130 | |||
131 | if (ends_with(result, calc->timedOutString())) { | ||
132 | throw timeout_exception(); | ||
133 | |||
134 | } else { | ||
135 | putchar(TYPE_RESULT); | ||
136 | putchar(approximate ? RESULT_APPROXIMATION_YES : RESULT_APPROXIMATION_NO); | ||
137 | fputs(result.c_str(), stdout); | ||
138 | } | ||
139 | putchar(SEPARATOR); | ||
140 | } | ||
141 | |||
142 | static EvaluationOptions get_evaluationoptions() { | ||
143 | EvaluationOptions eo; | ||
144 | eo.approximation = APPROXIMATION_TRY_EXACT; | ||
145 | eo.parse_options.unknowns_enabled = false; | ||
146 | eo.sync_units = false; | ||
147 | return eo; | ||
148 | } | ||
149 | |||
150 | static PrintOptions get_printoptions(int base, bool &approximate) { | ||
151 | PrintOptions po; | ||
152 | po.base = base; | ||
153 | po.number_fraction_format = FRACTION_DECIMAL; | ||
154 | po.interval_display = INTERVAL_DISPLAY_PLUSMINUS; | ||
155 | po.use_unicode_signs = true; | ||
156 | //po.min_decimals = MIN_DECIMALS; | ||
157 | po.time_zone = TIME_ZONE_UTC; | ||
158 | po.abbreviate_names = true; | ||
159 | po.spell_out_logical_operators = true; | ||
160 | po.allow_non_usable = true; | ||
161 | po.show_ending_zeroes = false; | ||
162 | //po.preserve_precision = true; | ||
163 | //po.restrict_to_parent_precision = false; | ||
164 | po.is_approximate = &approximate; | ||
165 | return po; | ||
166 | } | ||
167 | |||
168 | static void evaluate(Calculator *calc, const vector<string> &expressions, unsigned int mode, int base) { | ||
169 | calc->setExchangeRatesWarningEnabled(false); | ||
170 | calc->loadExchangeRates(); | ||
171 | calc->loadGlobalDefinitions(); | ||
172 | |||
173 | bool approximate = false; | ||
174 | |||
175 | PrintOptions po = get_printoptions(base, approximate); | ||
176 | EvaluationOptions eo = get_evaluationoptions(); | ||
177 | set_precision(calc, mode, eo, po); | ||
178 | |||
179 | calc->setMessagePrintOptions(po); | ||
180 | |||
181 | do_seccomp(); | ||
182 | |||
183 | auto result_struct = evaluate_all(expressions, eo, calc); | ||
184 | |||
185 | print_result(calc, result_struct, po, mode, approximate); | ||
186 | } | ||
187 | |||
188 | static void update() { | ||
189 | if (!CALCULATOR->canFetch()) | ||
190 | throw exchange_update_exception(); | ||
191 | |||
192 | CALCULATOR->fetchExchangeRates(TIMEOUT_UPDATE); | ||
193 | // for some reason this returns false even when it's successful so I'm not going to check it | ||
194 | } | ||
195 | |||
196 | static vector<string> parseExpressions(stringstream input) { | ||
197 | vector<string> result; | ||
198 | string expression; | ||
199 | while (std::getline(input, expression, '\n')) | ||
200 | result.push_back(expression); | ||
201 | |||
202 | return result; | ||
203 | } | ||
204 | |||
205 | int main(int argc, char **argv) { | ||
206 | do_setuid(); | ||
207 | |||
208 | if (argc < 2) | ||
209 | return 1; | ||
210 | |||
211 | auto *calc = new Calculator(true); | ||
212 | do_defang_calculator(calc); | ||
213 | try { | ||
214 | if (argc == 2) { | ||
215 | if (strcmp(argv[1], COMMAND_UPDATE) == 0) | ||
216 | update(); | ||
217 | else | ||
218 | return 1; | ||
219 | } else { | ||
220 | evaluate(calc, parseExpressions(stringstream(argv[1])), std::strtoul(argv[2], nullptr, 10), | ||
221 | std::strtol(argv[3], nullptr, 10)); | ||
222 | } | ||
223 | |||
224 | } catch (const qalculate_exception &e) { | ||
225 | return e.getCode(); | ||
226 | } | ||
227 | } | ||
228 | |||
diff --git a/src/qalculate_exception.cpp b/src/qalculate_exception.cpp new file mode 100644 index 0000000..db38fa3 --- /dev/null +++ b/src/qalculate_exception.cpp | |||
@@ -0,0 +1,12 @@ | |||
1 | //SPDX-License-Identifier: GPL-3.0 | ||
2 | |||
3 | #include <qalculate_exception.h> | ||
4 | #include <string> | ||
5 | |||
6 | qalculate_exception::qalculate_exception(int code) : | ||
7 | std::runtime_error(std::to_string(code).c_str()), code(code) { | ||
8 | } | ||
9 | |||
10 | int qalculate_exception::getCode() const { | ||
11 | return this->code; | ||
12 | } | ||
diff --git a/src/security_util.cpp b/src/security_util.cpp new file mode 100644 index 0000000..33728fc --- /dev/null +++ b/src/security_util.cpp | |||
@@ -0,0 +1,82 @@ | |||
1 | //SPDX-License-Identifier: GPL-3.0 | ||
2 | |||
3 | #include <libqalculate/Function.h> | ||
4 | #include <libqalculate/Variable.h> | ||
5 | #include <security_util.h> | ||
6 | |||
7 | #ifdef UID | ||
8 | #include <grp.h> | ||
9 | #include <cap-ng.h> | ||
10 | #else | ||
11 | #warning "Not doing setuid/setgid, do not use in production!" | ||
12 | #endif | ||
13 | |||
14 | #ifdef SECCOMP | ||
15 | #include <seccomp.h> | ||
16 | #else | ||
17 | #warning "Not doing seccomp, do not use in production!" | ||
18 | #endif | ||
19 | |||
20 | void do_setuid() { | ||
21 | #ifdef UID | ||
22 | if (setgroups(0, {})) { | ||
23 | perror("couldn't remove groups"); | ||
24 | abort(); | ||
25 | } | ||
26 | |||
27 | if (setresgid(UID, UID, UID)) { | ||
28 | perror("couldn't set gid"); | ||
29 | abort(); | ||
30 | } | ||
31 | |||
32 | if (setresuid(UID, UID, UID)) { | ||
33 | perror("couldn't set uid"); | ||
34 | abort(); | ||
35 | } | ||
36 | |||
37 | capng_clear(CAPNG_SELECT_BOTH); | ||
38 | if (capng_update(CAPNG_DROP, (capng_type_t)(CAPNG_EFFECTIVE | CAPNG_PERMITTED), CAP_SETGID)) { | ||
39 | printf("couldn't drop caps: can't select\n"); | ||
40 | abort(); | ||
41 | } | ||
42 | int err = capng_apply(CAPNG_SELECT_BOTH); | ||
43 | if (err) { | ||
44 | printf("couldn't drop caps: %d\n", err); | ||
45 | abort(); | ||
46 | } | ||
47 | #endif | ||
48 | } | ||
49 | |||
50 | void do_defang_calculator(Calculator *calc) { | ||
51 | calc->getActiveFunction("command")->destroy(); // rce | ||
52 | #ifdef HAS_PLOT | ||
53 | calc->getActiveFunction("plot")->destroy(); // wouldn't work | ||
54 | #endif | ||
55 | calc->getActiveVariable("uptime")->destroy(); // information leakage | ||
56 | } | ||
57 | |||
58 | void do_seccomp() { | ||
59 | #ifdef SECCOMP | ||
60 | scmp_filter_ctx ctx; | ||
61 | ctx = seccomp_init(SCMP_ACT_KILL); | ||
62 | /* 0 */ seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0); | ||
63 | /* 1 */ seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0); | ||
64 | /* 9 */ seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mmap), 0); | ||
65 | /* 10 */ seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mprotect), 0); | ||
66 | /* 11 */ seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(munmap), 0); | ||
67 | /* 13 */ seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(rt_sigaction), 0); | ||
68 | /* 14 */ seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(rt_sigprocmask), 0); | ||
69 | /* 24 */ seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(sched_yield), 0); | ||
70 | /* 230 */ seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(clock_nanosleep), 0); | ||
71 | /* 231 */ seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0); | ||
72 | /* 262 */ seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(newfstatat), 0); | ||
73 | /* 273 */ seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(set_robust_list), 0); | ||
74 | /* 334 */ seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(rseq), 0); | ||
75 | /* 435 */ seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(clone3), 0); | ||
76 | int err = seccomp_load(ctx); | ||
77 | if (err) { | ||
78 | printf("couldn't seccomp: %d\n", err); | ||
79 | abort(); | ||
80 | } | ||
81 | #endif | ||
82 | } | ||
diff --git a/src/timeout_exception.cpp b/src/timeout_exception.cpp new file mode 100644 index 0000000..09d4bf3 --- /dev/null +++ b/src/timeout_exception.cpp | |||
@@ -0,0 +1,7 @@ | |||
1 | //SPDX-License-Identifier: GPL-3.0 | ||
2 | |||
3 | #include <timeout_exception.h> | ||
4 | |||
5 | timeout_exception::timeout_exception() : | ||
6 | qalculate_exception(102) { | ||
7 | } | ||