1 /* Copyright (C) 2013-2018 Open Information Security Foundation
2 *
3 * You can copy, redistribute or modify this Program under the terms of
4 * the GNU General Public License version 2 as published by the Free
5 * Software Foundation.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * version 2 along with this program; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
15 * 02110-1301, USA.
16 */
17
18 /**
19 * \file
20 *
21 * \author Eric Leblond <eric@regit.org>
22 */
23
24 #include "suricata-common.h"
25 #include "suricata.h"
26 #include "unix-manager.h"
27 #include "detect-engine.h"
28 #include "tm-threads.h"
29 #include "runmodes.h"
30 #include "conf.h"
31
32 #include "output-json-stats.h"
33
34 #include "util-privs.h"
35 #include "util-debug.h"
36 #include "util-device.h"
37 #include "util-ebpf.h"
38 #include "util-signal.h"
39 #include "util-buffer.h"
40
41 #if (defined BUILD_UNIX_SOCKET) && (defined HAVE_SYS_UN_H) && (defined HAVE_SYS_STAT_H) && (defined HAVE_SYS_TYPES_H)
42 #include <sys/un.h>
43 #include <sys/stat.h>
44 #include <sys/types.h>
45
46 #include "output.h"
47 #include "output-json.h"
48
49 // MSG_NOSIGNAL does not exists on OS X
50 #ifdef OS_DARWIN
51 # ifndef MSG_NOSIGNAL
52 # define MSG_NOSIGNAL SO_NOSIGPIPE
53 # endif
54 #endif
55
56 #define SOCKET_PATH LOCAL_STATE_DIR "/run/suricata/"
57 #define SOCKET_FILENAME "suricata-command.socket"
58 #define SOCKET_TARGET SOCKET_PATH SOCKET_FILENAME
59
60 SCCtrlCondT unix_manager_ctrl_cond;
61 SCCtrlMutex unix_manager_ctrl_mutex;
62
63 #define MAX_FAILED_RULES 20
64
65 typedef struct Command_ {
66 char *name;
67 TmEcode (*Func)(json_t *, json_t *, void *);
68 void *data;
69 int flags;
70 TAILQ_ENTRY(Command_) next;
71 } Command;
72
73 typedef struct Task_ {
74 TmEcode (*Func)(void *);
75 void *data;
76 TAILQ_ENTRY(Task_) next;
77 } Task;
78
79 #define CLIENT_BUFFER_SIZE 4096
80 typedef struct UnixClient_ {
81 int fd;
82 MemBuffer *mbuf; /**< buffer for response construction */
83 int version;
84 TAILQ_ENTRY(UnixClient_) next;
85 } UnixClient;
86
87 typedef struct UnixCommand_ {
88 time_t start_timestamp;
89 int socket;
90 struct sockaddr_un client_addr;
91 int select_max;
92 TAILQ_HEAD(, Command_) commands;
93 TAILQ_HEAD(, Task_) tasks;
94 TAILQ_HEAD(, UnixClient_) clients;
95 } UnixCommand;
96
97 /**
98 * \brief Create a command unix socket on system
99 *
100 * \retval 0 in case of error, 1 in case of success
101 */
UnixNew(UnixCommand * this)102 static int UnixNew(UnixCommand * this)
103 {
104 struct sockaddr_un addr;
105 int len;
106 int ret;
107 int on = 1;
108 char sockettarget[PATH_MAX];
109 const char *socketname;
110
111 this->start_timestamp = time(NULL);
112 this->socket = -1;
113 this->select_max = 0;
114
115 TAILQ_INIT(&this->commands);
116 TAILQ_INIT(&this->tasks);
117 TAILQ_INIT(&this->clients);
118
119 int check_dir = 0;
120 if (ConfGet("unix-command.filename", &socketname) == 1) {
121 if (PathIsAbsolute(socketname)) {
122 strlcpy(sockettarget, socketname, sizeof(sockettarget));
123 } else {
124 snprintf(sockettarget, sizeof(sockettarget), "%s/%s",
125 SOCKET_PATH, socketname);
126 check_dir = 1;
127 }
128 } else {
129 strlcpy(sockettarget, SOCKET_TARGET, sizeof(sockettarget));
130 check_dir = 1;
131 }
132 SCLogInfo("Using unix socket file '%s'", sockettarget);
133
134 if (check_dir) {
135 struct stat stat_buf;
136 /* coverity[toctou] */
137 if (stat(SOCKET_PATH, &stat_buf) != 0) {
138 /* coverity[toctou] */
139 ret = SCMkDir(SOCKET_PATH, S_IRWXU|S_IXGRP|S_IRGRP);
140 if (ret != 0) {
141 int err = errno;
142 if (err != EEXIST) {
143 SCLogError(SC_ERR_INITIALIZATION,
144 "Cannot create socket directory %s: %s",
145 SOCKET_PATH, strerror(err));
146 return 0;
147 }
148 } else {
149 SCLogInfo("Created socket directory %s",
150 SOCKET_PATH);
151 }
152 }
153 }
154
155 /* Remove socket file */
156 (void) unlink(sockettarget);
157
158 /* set address */
159 addr.sun_family = AF_UNIX;
160 strlcpy(addr.sun_path, sockettarget, sizeof(addr.sun_path));
161 addr.sun_path[sizeof(addr.sun_path) - 1] = 0;
162 len = strlen(addr.sun_path) + sizeof(addr.sun_family) + 1;
163
164 /* create socket */
165 this->socket = socket(AF_UNIX, SOCK_STREAM, 0);
166 if (this->socket == -1) {
167 SCLogWarning(SC_ERR_OPENING_FILE,
168 "Unix Socket: unable to create UNIX socket %s: %s",
169 addr.sun_path, strerror(errno));
170 return 0;
171 }
172 this->select_max = this->socket + 1;
173
174 /* set reuse option */
175 ret = setsockopt(this->socket, SOL_SOCKET, SO_REUSEADDR,
176 (char *) &on, sizeof(on));
177 if ( ret != 0 ) {
178 SCLogWarning(SC_ERR_INITIALIZATION,
179 "Cannot set sockets options: %s.", strerror(errno));
180 }
181
182 /* bind socket */
183 ret = bind(this->socket, (struct sockaddr *) &addr, len);
184 if (ret == -1) {
185 SCLogWarning(SC_ERR_INITIALIZATION,
186 "Unix socket: UNIX socket bind(%s) error: %s",
187 sockettarget, strerror(errno));
188 return 0;
189 }
190
191 #if !(defined OS_FREEBSD || defined __OpenBSD__ || defined __DragonFly__)
192 /* Set file mode: will not fully work on most system, the group
193 * permission is not changed on some Linux. *BSD won't do the
194 * chmod: it returns EINVAL when calling chmod on sockets. */
195 ret = chmod(sockettarget, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP);
196 if (ret == -1) {
197 int err = errno;
198 SCLogWarning(SC_ERR_INITIALIZATION,
199 "Unable to change permission on socket: %s (%d)",
200 strerror(err),
201 err);
202 }
203 #endif
204
205 /* listen */
206 if (listen(this->socket, 1) == -1) {
207 SCLogWarning(SC_ERR_INITIALIZATION,
208 "Command server: UNIX socket listen() error: %s",
209 strerror(errno));
210 return 0;
211 }
212 return 1;
213 }
214
UnixCommandSetMaxFD(UnixCommand * this)215 static void UnixCommandSetMaxFD(UnixCommand *this)
216 {
217 UnixClient *item;
218
219 if (this == NULL) {
220 SCLogError(SC_ERR_INVALID_ARGUMENT, "Unix command is NULL, warn devel");
221 return;
222 }
223
224 this->select_max = this->socket + 1;
225 TAILQ_FOREACH(item, &this->clients, next) {
226 if (item->fd >= this->select_max) {
227 this->select_max = item->fd + 1;
228 }
229 }
230 }
231
UnixClientAlloc(void)232 static UnixClient *UnixClientAlloc(void)
233 {
234 UnixClient *uclient = SCMalloc(sizeof(UnixClient));
235 if (unlikely(uclient == NULL)) {
236 SCLogError(SC_ERR_MEM_ALLOC, "Can't allocate new client");
237 return NULL;
238 }
239 uclient->mbuf = MemBufferCreateNew(CLIENT_BUFFER_SIZE);
240 if (uclient->mbuf == NULL) {
241 SCLogError(SC_ERR_MEM_ALLOC, "Can't allocate new client send buffer");
242 SCFree(uclient);
243 return NULL;
244 }
245 return uclient;
246 }
247
UnixClientFree(UnixClient * c)248 static void UnixClientFree(UnixClient *c)
249 {
250 if (c != NULL) {
251 MemBufferFree(c->mbuf);
252 SCFree(c);
253 }
254 }
255
256 /**
257 * \brief Close the unix socket
258 */
UnixCommandClose(UnixCommand * this,int fd)259 static void UnixCommandClose(UnixCommand *this, int fd)
260 {
261 UnixClient *item;
262 int found = 0;
263
264 TAILQ_FOREACH(item, &this->clients, next) {
265 if (item->fd == fd) {
266 found = 1;
267 break;
268 }
269 }
270
271 if (found == 0) {
272 SCLogError(SC_ERR_INVALID_VALUE, "No fd found in client list");
273 return;
274 }
275
276 TAILQ_REMOVE(&this->clients, item, next);
277
278 close(item->fd);
279 UnixCommandSetMaxFD(this);
280 UnixClientFree(item);
281 }
282
283 #define UNIX_PROTO_VERSION_LENGTH 200
284 #define UNIX_PROTO_VERSION_V1 "0.1"
285 #define UNIX_PROTO_V1 1
286 #define UNIX_PROTO_VERSION "0.2"
287 #define UNIX_PROTO_V2 2
288
UnixCommandSendJSONToClient(UnixClient * client,json_t * js)289 static int UnixCommandSendJSONToClient(UnixClient *client, json_t *js)
290 {
291 MemBufferReset(client->mbuf);
292
293 OutputJSONMemBufferWrapper wrapper = {
294 .buffer = &client->mbuf,
295 .expand_by = CLIENT_BUFFER_SIZE
296 };
297
298 int r = json_dump_callback(js, OutputJSONMemBufferCallback, &wrapper,
299 JSON_PRESERVE_ORDER|JSON_COMPACT|JSON_ENSURE_ASCII|
300 JSON_ESCAPE_SLASH);
301 if (r != 0) {
302 SCLogWarning(SC_ERR_SOCKET, "unable to serialize JSON object");
303 return -1;
304 }
305
306 if (client->version > UNIX_PROTO_V1) {
307 if (MEMBUFFER_OFFSET(client->mbuf) + 1 >= MEMBUFFER_SIZE(client->mbuf)) {
308 MemBufferExpand(&client->mbuf, 1);
309 }
310 MemBufferWriteRaw(client->mbuf, "\n", 1);
311 }
312
313 if (send(client->fd, (const char *)MEMBUFFER_BUFFER(client->mbuf),
314 MEMBUFFER_OFFSET(client->mbuf), MSG_NOSIGNAL) == -1)
315 {
316 SCLogWarning(SC_ERR_SOCKET, "unable to send block of size "
317 "%"PRIuMAX": %s", (uintmax_t)MEMBUFFER_OFFSET(client->mbuf),
318 strerror(errno));
319 return -1;
320 }
321
322 SCLogDebug("sent message of size %"PRIuMAX" to client socket %d",
323 (uintmax_t)MEMBUFFER_OFFSET(client->mbuf), client->fd);
324 return 0;
325 }
326
327 /**
328 * \brief Accept a new client on unix socket
329 *
330 * The function is called when a new user is detected
331 * in UnixMain(). It does the initial protocol negotiation
332 * with client.
333 *
334 * \retval 0 in case of error, 1 in case of success
335 */
UnixCommandAccept(UnixCommand * this)336 static int UnixCommandAccept(UnixCommand *this)
337 {
338 char buffer[UNIX_PROTO_VERSION_LENGTH + 1];
339 json_t *client_msg;
340 json_t *server_msg;
341 json_t *version;
342 json_error_t jerror;
343 int client;
344 int client_version;
345 int ret;
346 UnixClient *uclient = NULL;
347
348 /* accept client socket */
349 socklen_t len = sizeof(this->client_addr);
350 client = accept(this->socket, (struct sockaddr *) &this->client_addr,
351 &len);
352 if (client < 0) {
353 SCLogInfo("Unix socket: accept() error: %s",
354 strerror(errno));
355 return 0;
356 }
357 SCLogDebug("Unix socket: client connection");
358
359 /* read client version */
360 buffer[sizeof(buffer)-1] = 0;
361 ret = recv(client, buffer, sizeof(buffer)-1, 0);
362 if (ret < 0) {
363 SCLogInfo("Command server: client doesn't send version");
364 close(client);
365 return 0;
366 }
367 if (ret >= (int)(sizeof(buffer)-1)) {
368 SCLogInfo("Command server: client message is too long, "
369 "disconnect him.");
370 close(client);
371 return 0;
372 }
373 buffer[ret] = 0;
374
375 client_msg = json_loads(buffer, 0, &jerror);
376 if (client_msg == NULL) {
377 SCLogInfo("Invalid command, error on line %d: %s\n", jerror.line, jerror.text);
378 close(client);
379 return 0;
380 }
381
382 version = json_object_get(client_msg, "version");
383 if (!json_is_string(version)) {
384 SCLogInfo("error: version is not a string");
385 close(client);
386 json_decref(client_msg);
387 return 0;
388 }
389
390 /* check client version */
391 if ((strcmp(json_string_value(version), UNIX_PROTO_VERSION) != 0)
392 && (strcmp(json_string_value(version), UNIX_PROTO_VERSION_V1) != 0)) {
393 SCLogInfo("Unix socket: invalid client version: \"%s\"",
394 json_string_value(version));
395 json_decref(client_msg);
396 close(client);
397 return 0;
398 } else {
399 SCLogDebug("Unix socket: client version: \"%s\"",
400 json_string_value(version));
401 if (strcmp(json_string_value(version), UNIX_PROTO_VERSION_V1) == 0) {
402 client_version = UNIX_PROTO_V1;
403 } else {
404 client_version = UNIX_PROTO_V2;
405 }
406 }
407
408 json_decref(client_msg);
409 /* send answer */
410 server_msg = json_object();
411 if (server_msg == NULL) {
412 close(client);
413 return 0;
414 }
415 json_object_set_new(server_msg, "return", json_string("OK"));
416
417 uclient = UnixClientAlloc();
418 if (unlikely(uclient == NULL)) {
419 SCLogError(SC_ERR_MEM_ALLOC, "Can't allocate new client");
420 json_decref(server_msg);
421 close(client);
422 return 0;
423 }
424 uclient->fd = client;
425 uclient->version = client_version;
426
427 if (UnixCommandSendJSONToClient(uclient, server_msg) != 0) {
428 SCLogWarning(SC_ERR_SOCKET, "Unable to send command");
429
430 UnixClientFree(uclient);
431 json_decref(server_msg);
432 close(client);
433 return 0;
434 }
435
436 json_decref(server_msg);
437
438 /* client connected */
439 SCLogDebug("Unix socket: client connected");
440 TAILQ_INSERT_TAIL(&this->clients, uclient, next);
441 UnixCommandSetMaxFD(this);
442 return 1;
443 }
444
UnixCommandBackgroundTasks(UnixCommand * this)445 static int UnixCommandBackgroundTasks(UnixCommand* this)
446 {
447 int ret = 1;
448 Task *ltask;
449
450 TAILQ_FOREACH(ltask, &this->tasks, next) {
451 int fret = ltask->Func(ltask->data);
452 if (fret != TM_ECODE_OK) {
453 ret = 0;
454 }
455 }
456 return ret;
457 }
458
459 /**
460 * \brief Command dispatcher
461 *
462 * \param this a UnixCommand:: structure
463 * \param command a string containing a json formatted
464 * command
465 *
466 * \retval 0 in case of error, 1 in case of success
467 */
UnixCommandExecute(UnixCommand * this,char * command,UnixClient * client)468 static int UnixCommandExecute(UnixCommand * this, char *command, UnixClient *client)
469 {
470 int ret = 1;
471 json_error_t error;
472 json_t *jsoncmd = NULL;
473 json_t *cmd = NULL;
474 json_t *server_msg = json_object();
475 const char * value;
476 int found = 0;
477 Command *lcmd;
478
479 if (server_msg == NULL) {
480 return 0;
481 }
482
483 jsoncmd = json_loads(command, 0, &error);
484 if (jsoncmd == NULL) {
485 SCLogInfo("Invalid command, error on line %d: %s\n", error.line, error.text);
486 goto error;
487 }
488
489 cmd = json_object_get(jsoncmd, "command");
490 if(!json_is_string(cmd)) {
491 SCLogInfo("error: command is not a string");
492 goto error_cmd;
493 }
494 value = json_string_value(cmd);
495
496 TAILQ_FOREACH(lcmd, &this->commands, next) {
497 if (!strcmp(value, lcmd->name)) {
498 int fret = TM_ECODE_OK;
499 found = 1;
500 if (lcmd->flags & UNIX_CMD_TAKE_ARGS) {
501 cmd = json_object_get(jsoncmd, "arguments");
502 if(!json_is_object(cmd)) {
503 SCLogInfo("error: argument is not an object");
504 goto error_cmd;
505 }
506 }
507 fret = lcmd->Func(cmd, server_msg, lcmd->data);
508 if (fret != TM_ECODE_OK) {
509 ret = 0;
510 }
511 break;
512 }
513 }
514
515 if (found == 0) {
516 json_object_set_new(server_msg, "message", json_string("Unknown command"));
517 ret = 0;
518 }
519
520 switch (ret) {
521 case 0:
522 json_object_set_new(server_msg, "return", json_string("NOK"));
523 break;
524 case 1:
525 json_object_set_new(server_msg, "return", json_string("OK"));
526 break;
527 }
528
529 if (UnixCommandSendJSONToClient(client, server_msg) != 0) {
530 goto error;
531 }
532
533 json_decref(jsoncmd);
534 json_decref(server_msg);
535 return ret;
536
537 error_cmd:
538 json_decref(jsoncmd);
539 error:
540 json_decref(server_msg);
541 UnixCommandClose(this, client->fd);
542 return 0;
543 }
544
UnixCommandRun(UnixCommand * this,UnixClient * client)545 static void UnixCommandRun(UnixCommand * this, UnixClient *client)
546 {
547 char buffer[4096];
548 int ret;
549 if (client->version <= UNIX_PROTO_V1) {
550 ret = recv(client->fd, buffer, sizeof(buffer) - 1, 0);
551 if (ret <= 0) {
552 if (ret == 0) {
553 SCLogDebug("Unix socket: lost connection with client");
554 } else {
555 SCLogError(SC_ERR_SOCKET, "Unix socket: error on recv() from client: %s",
556 strerror(errno));
557 }
558 UnixCommandClose(this, client->fd);
559 return;
560 }
561 if (ret >= (int)(sizeof(buffer)-1)) {
562 SCLogError(SC_ERR_SOCKET, "Command server: client command is too long, "
563 "disconnect him.");
564 UnixCommandClose(this, client->fd);
565 }
566 buffer[ret] = 0;
567 } else {
568 int try = 0;
569 int offset = 0;
570 int cmd_over = 0;
571 ret = recv(client->fd, buffer + offset, sizeof(buffer) - offset - 1, 0);
572 do {
573 if (ret <= 0) {
574 if (ret == 0) {
575 SCLogInfo("Unix socket: lost connection with client");
576 } else {
577 SCLogInfo("Unix socket: error on recv() from client: %s",
578 strerror(errno));
579 }
580 UnixCommandClose(this, client->fd);
581 return;
582 }
583 if (ret >= (int)(sizeof(buffer)- offset - 1)) {
584 SCLogInfo("Command server: client command is too long, "
585 "disconnect him.");
586 UnixCommandClose(this, client->fd);
587 }
588 if (buffer[ret - 1] == '\n') {
589 buffer[ret-1] = 0;
590 cmd_over = 1;
591 } else {
592 struct timeval tv;
593 fd_set select_set;
594 offset += ret;
595 do {
596 FD_ZERO(&select_set);
597 FD_SET(client->fd, &select_set);
598 tv.tv_sec = 0;
599 tv.tv_usec = 200 * 1000;
600 try++;
601 ret = select(client->fd, &select_set, NULL, NULL, &tv);
602 /* catch select() error */
603 if (ret == -1) {
604 /* Signal was caught: just ignore it */
605 if (errno != EINTR) {
606 SCLogInfo("Unix socket: lost connection with client");
607 UnixCommandClose(this, client->fd);
608 return;
609 }
610 }
611 } while (ret == 0 && try < 3);
612 if (ret > 0) {
613 ret = recv(client->fd, buffer + offset,
614 sizeof(buffer) - offset - 1, 0);
615 }
616 }
617 } while (try < 3 && cmd_over == 0);
618
619 if (try == 3 && cmd_over == 0) {
620 SCLogInfo("Unix socket: imcomplete client message, closing connection");
621 UnixCommandClose(this, client->fd);
622 return;
623 }
624 }
625 UnixCommandExecute(this, buffer, client);
626 }
627
628 /**
629 * \brief Select function
630 *
631 * \retval 0 in case of error, 1 in case of success
632 */
UnixMain(UnixCommand * this)633 static int UnixMain(UnixCommand * this)
634 {
635 struct timeval tv;
636 int ret;
637 fd_set select_set;
638 UnixClient *uclient;
639 UnixClient *tclient;
640
641 /* Wait activity on the socket */
642 FD_ZERO(&select_set);
643 FD_SET(this->socket, &select_set);
644 TAILQ_FOREACH(uclient, &this->clients, next) {
645 FD_SET(uclient->fd, &select_set);
646 }
647
648 tv.tv_sec = 0;
649 tv.tv_usec = 200 * 1000;
650 ret = select(this->select_max, &select_set, NULL, NULL, &tv);
651
652 /* catch select() error */
653 if (ret == -1) {
654 /* Signal was caught: just ignore it */
655 if (errno == EINTR) {
656 return 1;
657 }
658 SCLogError(SC_ERR_SOCKET, "Command server: select() fatal error: %s", strerror(errno));
659 return 0;
660 }
661
662 if (suricata_ctl_flags & SURICATA_STOP) {
663 TAILQ_FOREACH_SAFE(uclient, &this->clients, next, tclient) {
664 UnixCommandClose(this, uclient->fd);
665 }
666 return 1;
667 }
668
669 /* timeout: continue */
670 if (ret == 0) {
671 return 1;
672 }
673
674 TAILQ_FOREACH_SAFE(uclient, &this->clients, next, tclient) {
675 if (FD_ISSET(uclient->fd, &select_set)) {
676 UnixCommandRun(this, uclient);
677 }
678 }
679 if (FD_ISSET(this->socket, &select_set)) {
680 if (!UnixCommandAccept(this))
681 return 1;
682 }
683
684 return 1;
685 }
686
UnixManagerShutdownCommand(json_t * cmd,json_t * server_msg,void * data)687 static TmEcode UnixManagerShutdownCommand(json_t *cmd,
688 json_t *server_msg, void *data)
689 {
690 SCEnter();
691 json_object_set_new(server_msg, "message", json_string("Closing Suricata"));
692 EngineStop();
693 SCReturnInt(TM_ECODE_OK);
694 }
695
UnixManagerVersionCommand(json_t * cmd,json_t * server_msg,void * data)696 static TmEcode UnixManagerVersionCommand(json_t *cmd,
697 json_t *server_msg, void *data)
698 {
699 SCEnter();
700 json_object_set_new(server_msg, "message", json_string(GetProgramVersion()));
701 SCReturnInt(TM_ECODE_OK);
702 }
703
UnixManagerUptimeCommand(json_t * cmd,json_t * server_msg,void * data)704 static TmEcode UnixManagerUptimeCommand(json_t *cmd,
705 json_t *server_msg, void *data)
706 {
707 SCEnter();
708 int uptime;
709 UnixCommand *ucmd = (UnixCommand *)data;
710
711 uptime = time(NULL) - ucmd->start_timestamp;
712 json_object_set_new(server_msg, "message", json_integer(uptime));
713 SCReturnInt(TM_ECODE_OK);
714 }
715
UnixManagerRunningModeCommand(json_t * cmd,json_t * server_msg,void * data)716 static TmEcode UnixManagerRunningModeCommand(json_t *cmd,
717 json_t *server_msg, void *data)
718 {
719 SCEnter();
720 json_object_set_new(server_msg, "message", json_string(RunmodeGetActive()));
721 SCReturnInt(TM_ECODE_OK);
722 }
723
UnixManagerCaptureModeCommand(json_t * cmd,json_t * server_msg,void * data)724 static TmEcode UnixManagerCaptureModeCommand(json_t *cmd,
725 json_t *server_msg, void *data)
726 {
727 SCEnter();
728 json_object_set_new(server_msg, "message", json_string(RunModeGetMainMode()));
729 SCReturnInt(TM_ECODE_OK);
730 }
731
UnixManagerReloadRulesWrapper(json_t * cmd,json_t * server_msg,void * data,int do_wait)732 static TmEcode UnixManagerReloadRulesWrapper(json_t *cmd, json_t *server_msg, void *data, int do_wait)
733 {
734 SCEnter();
735
736 if (SuriHasSigFile()) {
737 json_object_set_new(server_msg, "message",
738 json_string("Live rule reload not possible if -s "
739 "or -S option used at runtime."));
740 SCReturnInt(TM_ECODE_FAILED);
741 }
742
743 int r = DetectEngineReloadStart();
744
745 if (r == 0 && do_wait) {
746 while (!DetectEngineReloadIsIdle())
747 usleep(100);
748 } else {
749 if (r == -1) {
750 json_object_set_new(server_msg, "message", json_string("Reload already in progress"));
751 SCReturnInt(TM_ECODE_FAILED);
752 }
753 }
754
755 json_object_set_new(server_msg, "message", json_string("done"));
756 SCReturnInt(TM_ECODE_OK);
757 }
758
UnixManagerReloadRules(json_t * cmd,json_t * server_msg,void * data)759 static TmEcode UnixManagerReloadRules(json_t *cmd, json_t *server_msg, void *data)
760 {
761 return UnixManagerReloadRulesWrapper(cmd, server_msg, data, 1);
762 }
763
UnixManagerNonBlockingReloadRules(json_t * cmd,json_t * server_msg,void * data)764 static TmEcode UnixManagerNonBlockingReloadRules(json_t *cmd, json_t *server_msg, void *data)
765 {
766 return UnixManagerReloadRulesWrapper(cmd, server_msg, data, 0);
767 }
768
UnixManagerReloadTimeCommand(json_t * cmd,json_t * server_msg,void * data)769 static TmEcode UnixManagerReloadTimeCommand(json_t *cmd,
770 json_t *server_msg, void *data)
771 {
772 SCEnter();
773 TmEcode retval;
774 json_t *jdata = NULL;
775
776 retval = OutputEngineStatsReloadTime(&jdata);
777 json_object_set_new(server_msg, "message", jdata);
778 SCReturnInt(retval);
779 }
780
UnixManagerRulesetStatsCommand(json_t * cmd,json_t * server_msg,void * data)781 static TmEcode UnixManagerRulesetStatsCommand(json_t *cmd,
782 json_t *server_msg, void *data)
783 {
784 SCEnter();
785 TmEcode retval;
786 json_t *jdata = NULL;
787
788 retval = OutputEngineStatsRuleset(&jdata);
789 json_object_set_new(server_msg, "message", jdata);
790 SCReturnInt(retval);
791 }
792
UnixManagerShowFailedRules(json_t * cmd,json_t * server_msg,void * data)793 static TmEcode UnixManagerShowFailedRules(json_t *cmd,
794 json_t *server_msg, void *data)
795 {
796 SCEnter();
797 int rules_cnt = 0;
798 DetectEngineCtx *de_ctx = DetectEngineGetCurrent();
799 if (de_ctx == NULL) {
800 json_object_set_new(server_msg, "message", json_string("Unable to get info"));
801 SCReturnInt(TM_ECODE_OK);
802 }
803
804 /* Since we need to deference de_ctx, we don't want to lost it. */
805 DetectEngineCtx *list = de_ctx;
806 json_t *js_sigs_array = json_array();
807
808 if (js_sigs_array == NULL) {
809 json_object_set_new(server_msg, "message", json_string("Unable to get info"));
810 goto error;
811 }
812 while (list) {
813 SigString *sigs_str = NULL;
814 TAILQ_FOREACH(sigs_str, &list->sig_stat.failed_sigs, next) {
815 json_t *jdata = json_object();
816 if (jdata == NULL) {
817 json_object_set_new(server_msg, "message", json_string("Unable to get the sig"));
818 goto error;
819 }
820
821 json_object_set_new(jdata, "tenant_id", json_integer(list->tenant_id));
822 json_object_set_new(jdata, "rule", json_string(sigs_str->sig_str));
823 json_object_set_new(jdata, "filename", json_string(sigs_str->filename));
824 json_object_set_new(jdata, "line", json_integer(sigs_str->line));
825 if (sigs_str->sig_error) {
826 json_object_set_new(jdata, "error", json_string(sigs_str->sig_error));
827 }
828 json_array_append_new(js_sigs_array, jdata);
829 if (++rules_cnt > MAX_FAILED_RULES) {
830 break;
831 }
832 }
833 if (rules_cnt > MAX_FAILED_RULES) {
834 break;
835 }
836 list = list->next;
837 }
838
839 json_object_set_new(server_msg, "message", js_sigs_array);
840 DetectEngineDeReference(&de_ctx);
841 SCReturnInt(TM_ECODE_OK);
842
843 error:
844 DetectEngineDeReference(&de_ctx);
845 json_object_clear(js_sigs_array);
846 json_decref(js_sigs_array);
847 SCReturnInt(TM_ECODE_FAILED);
848 }
849
UnixManagerConfGetCommand(json_t * cmd,json_t * server_msg,void * data)850 static TmEcode UnixManagerConfGetCommand(json_t *cmd,
851 json_t *server_msg, void *data)
852 {
853 SCEnter();
854
855 const char *confval = NULL;
856 char *variable = NULL;
857
858 json_t *jarg = json_object_get(cmd, "variable");
859 if(!json_is_string(jarg)) {
860 SCLogInfo("error: variable is not a string");
861 json_object_set_new(server_msg, "message", json_string("variable is not a string"));
862 SCReturnInt(TM_ECODE_FAILED);
863 }
864
865 variable = (char *)json_string_value(jarg);
866 if (ConfGet(variable, &confval) != 1) {
867 json_object_set_new(server_msg, "message", json_string("Unable to get value"));
868 SCReturnInt(TM_ECODE_FAILED);
869 }
870
871 if (confval) {
872 json_object_set_new(server_msg, "message", json_string(confval));
873 SCReturnInt(TM_ECODE_OK);
874 }
875
876 json_object_set_new(server_msg, "message", json_string("No string value"));
877 SCReturnInt(TM_ECODE_FAILED);
878 }
879
UnixManagerListCommand(json_t * cmd,json_t * answer,void * data)880 static TmEcode UnixManagerListCommand(json_t *cmd,
881 json_t *answer, void *data)
882 {
883 SCEnter();
884 json_t *jdata;
885 json_t *jarray;
886 Command *lcmd = NULL;
887 UnixCommand *gcmd = (UnixCommand *) data;
888 int i = 0;
889
890 jdata = json_object();
891 if (jdata == NULL) {
892 json_object_set_new(answer, "message",
893 json_string("internal error at json object creation"));
894 return TM_ECODE_FAILED;
895 }
896 jarray = json_array();
897 if (jarray == NULL) {
898 json_object_set_new(answer, "message",
899 json_string("internal error at json object creation"));
900 return TM_ECODE_FAILED;
901 }
902
903 TAILQ_FOREACH(lcmd, &gcmd->commands, next) {
904 json_array_append_new(jarray, json_string(lcmd->name));
905 i++;
906 }
907
908 json_object_set_new(jdata, "count", json_integer(i));
909 json_object_set_new(jdata, "commands", jarray);
910 json_object_set_new(answer, "message", jdata);
911 SCReturnInt(TM_ECODE_OK);
912 }
913
UnixManagerReopenLogFiles(json_t * cmd,json_t * server_msg,void * data)914 static TmEcode UnixManagerReopenLogFiles(json_t *cmd, json_t *server_msg, void *data)
915 {
916 OutputNotifyFileRotation();
917 json_object_set_new(server_msg, "message", json_string("done"));
918 SCReturnInt(TM_ECODE_OK);
919 }
920
921 #if 0
922 TmEcode UnixManagerReloadRules(json_t *cmd,
923 json_t *server_msg, void *data)
924 {
925 SCEnter();
926 if (suricata_ctl_flags != 0) {
927 json_object_set_new(server_msg, "message",
928 json_string("Live rule swap no longer possible."
929 " Engine in shutdown mode."));
930 SCReturn(TM_ECODE_FAILED);
931 } else {
932 /* FIXME : need to check option value */
933 UtilSignalHandlerSetup(SIGUSR2, SignalHandlerSigusr2Idle);
934 DetectEngineSpawnLiveRuleSwapMgmtThread();
935 json_object_set_new(server_msg, "message", json_string("Reloading rules"));
936 }
937 SCReturn(TM_ECODE_OK);
938 }
939 #endif
940
941 static UnixCommand command;
942
943 /**
944 * \brief Add a command to the list of commands
945 *
946 * This function adds a command to the list of commands available
947 * through the unix socket.
948 *
949 * When a command is received from user through the unix socket, the content
950 * of 'Command' field in the JSON message is match against keyword, then the
951 * Func is called. See UnixSocketAddPcapFile() for an example.
952 *
953 * \param keyword name of the command
954 * \param Func function to run when command is received
955 * \param data a pointer to data that are passed to Func when it is run
956 * \param flags a flag now used to tune the command type
957 * \retval TM_ECODE_OK in case of success, TM_ECODE_FAILED in case of failure
958 */
UnixManagerRegisterCommand(const char * keyword,TmEcode (* Func)(json_t *,json_t *,void *),void * data,int flags)959 TmEcode UnixManagerRegisterCommand(const char * keyword,
960 TmEcode (*Func)(json_t *, json_t *, void *),
961 void *data, int flags)
962 {
963 SCEnter();
964 Command *cmd = NULL;
965 Command *lcmd = NULL;
966
967 if (Func == NULL) {
968 SCLogError(SC_ERR_INVALID_ARGUMENT, "Null function");
969 SCReturnInt(TM_ECODE_FAILED);
970 }
971
972 if (keyword == NULL) {
973 SCLogError(SC_ERR_INVALID_ARGUMENT, "Null keyword");
974 SCReturnInt(TM_ECODE_FAILED);
975 }
976
977 TAILQ_FOREACH(lcmd, &command.commands, next) {
978 if (!strcmp(keyword, lcmd->name)) {
979 SCLogError(SC_ERR_INVALID_ARGUMENT, "%s already registered", keyword);
980 SCReturnInt(TM_ECODE_FAILED);
981 }
982 }
983
984 cmd = SCMalloc(sizeof(Command));
985 if (unlikely(cmd == NULL)) {
986 SCLogError(SC_ERR_MEM_ALLOC, "Can't alloc cmd");
987 SCReturnInt(TM_ECODE_FAILED);
988 }
989 cmd->name = SCStrdup(keyword);
990 if (unlikely(cmd->name == NULL)) {
991 SCLogError(SC_ERR_MEM_ALLOC, "Can't alloc cmd name");
992 SCFree(cmd);
993 SCReturnInt(TM_ECODE_FAILED);
994 }
995 cmd->Func = Func;
996 cmd->data = data;
997 cmd->flags = flags;
998 /* Add it to the list */
999 TAILQ_INSERT_TAIL(&command.commands, cmd, next);
1000
1001 SCReturnInt(TM_ECODE_OK);
1002 }
1003
1004 /**
1005 * \brief Add a task to the list of tasks
1006 *
1007 * This function adds a task to run in the background. The task is run
1008 * each time the UnixMain() function exits from select.
1009 *
1010 * \param Func function to run when a command is received
1011 * \param data a pointer to data that are passed to Func when it is run
1012 * \retval TM_ECODE_OK in case of success, TM_ECODE_FAILED in case of failure
1013 */
UnixManagerRegisterBackgroundTask(TmEcode (* Func)(void *),void * data)1014 TmEcode UnixManagerRegisterBackgroundTask(TmEcode (*Func)(void *),
1015 void *data)
1016 {
1017 SCEnter();
1018 Task *task = NULL;
1019
1020 if (Func == NULL) {
1021 SCLogError(SC_ERR_INVALID_ARGUMENT, "Null function");
1022 SCReturnInt(TM_ECODE_FAILED);
1023 }
1024
1025 task = SCMalloc(sizeof(Task));
1026 if (unlikely(task == NULL)) {
1027 SCLogError(SC_ERR_MEM_ALLOC, "Can't alloc task");
1028 SCReturnInt(TM_ECODE_FAILED);
1029 }
1030 task->Func = Func;
1031 task->data = data;
1032 /* Add it to the list */
1033 TAILQ_INSERT_TAIL(&command.tasks, task, next);
1034
1035 SCReturnInt(TM_ECODE_OK);
1036 }
1037
UnixManagerInit(void)1038 int UnixManagerInit(void)
1039 {
1040 if (UnixNew(&command) == 0) {
1041 int failure_fatal = 0;
1042 if (ConfGetBool("engine.init-failure-fatal", &failure_fatal) != 1) {
1043 SCLogDebug("ConfGetBool could not load the value.");
1044 }
1045 if (failure_fatal) {
1046 FatalError(SC_ERR_FATAL,
1047 "Unable to create unix command socket");
1048 } else {
1049 SCLogWarning(SC_ERR_INITIALIZATION,
1050 "Unable to create unix command socket");
1051 return -1;
1052 }
1053 }
1054
1055 /* Init Unix socket */
1056 UnixManagerRegisterCommand("shutdown", UnixManagerShutdownCommand, NULL, 0);
1057 UnixManagerRegisterCommand("command-list", UnixManagerListCommand, &command, 0);
1058 UnixManagerRegisterCommand("help", UnixManagerListCommand, &command, 0);
1059 UnixManagerRegisterCommand("version", UnixManagerVersionCommand, &command, 0);
1060 UnixManagerRegisterCommand("uptime", UnixManagerUptimeCommand, &command, 0);
1061 UnixManagerRegisterCommand("running-mode", UnixManagerRunningModeCommand, &command, 0);
1062 UnixManagerRegisterCommand("capture-mode", UnixManagerCaptureModeCommand, &command, 0);
1063 UnixManagerRegisterCommand("conf-get", UnixManagerConfGetCommand, &command, UNIX_CMD_TAKE_ARGS);
1064 UnixManagerRegisterCommand("dump-counters", StatsOutputCounterSocket, NULL, 0);
1065 UnixManagerRegisterCommand("reload-rules", UnixManagerReloadRules, NULL, 0);
1066 UnixManagerRegisterCommand("ruleset-reload-rules", UnixManagerReloadRules, NULL, 0);
1067 UnixManagerRegisterCommand("ruleset-reload-nonblocking", UnixManagerNonBlockingReloadRules, NULL, 0);
1068 UnixManagerRegisterCommand("ruleset-reload-time", UnixManagerReloadTimeCommand, NULL, 0);
1069 UnixManagerRegisterCommand("ruleset-stats", UnixManagerRulesetStatsCommand, NULL, 0);
1070 UnixManagerRegisterCommand("ruleset-failed-rules", UnixManagerShowFailedRules, NULL, 0);
1071 UnixManagerRegisterCommand("register-tenant-handler", UnixSocketRegisterTenantHandler, &command, UNIX_CMD_TAKE_ARGS);
1072 UnixManagerRegisterCommand("unregister-tenant-handler", UnixSocketUnregisterTenantHandler, &command, UNIX_CMD_TAKE_ARGS);
1073 UnixManagerRegisterCommand("register-tenant", UnixSocketRegisterTenant, &command, UNIX_CMD_TAKE_ARGS);
1074 UnixManagerRegisterCommand("reload-tenant", UnixSocketReloadTenant, &command, UNIX_CMD_TAKE_ARGS);
1075 UnixManagerRegisterCommand("unregister-tenant", UnixSocketUnregisterTenant, &command, UNIX_CMD_TAKE_ARGS);
1076 UnixManagerRegisterCommand("add-hostbit", UnixSocketHostbitAdd, &command, UNIX_CMD_TAKE_ARGS);
1077 UnixManagerRegisterCommand("remove-hostbit", UnixSocketHostbitRemove, &command, UNIX_CMD_TAKE_ARGS);
1078 UnixManagerRegisterCommand("list-hostbit", UnixSocketHostbitList, &command, UNIX_CMD_TAKE_ARGS);
1079 UnixManagerRegisterCommand("reopen-log-files", UnixManagerReopenLogFiles, NULL, 0);
1080 UnixManagerRegisterCommand("memcap-set", UnixSocketSetMemcap, &command, UNIX_CMD_TAKE_ARGS);
1081 UnixManagerRegisterCommand("memcap-show", UnixSocketShowMemcap, &command, UNIX_CMD_TAKE_ARGS);
1082 UnixManagerRegisterCommand("memcap-list", UnixSocketShowAllMemcap, NULL, 0);
1083
1084 UnixManagerRegisterCommand("dataset-add", UnixSocketDatasetAdd, &command, UNIX_CMD_TAKE_ARGS);
1085 UnixManagerRegisterCommand("dataset-remove", UnixSocketDatasetRemove, &command, UNIX_CMD_TAKE_ARGS);
1086
1087 return 0;
1088 }
1089
1090 typedef struct UnixManagerThreadData_ {
1091 int padding;
1092 } UnixManagerThreadData;
1093
UnixManagerThreadInit(ThreadVars * t,const void * initdata,void ** data)1094 static TmEcode UnixManagerThreadInit(ThreadVars *t, const void *initdata, void **data)
1095 {
1096 UnixManagerThreadData *utd = SCCalloc(1, sizeof(*utd));
1097 if (utd == NULL)
1098 return TM_ECODE_FAILED;
1099
1100 *data = utd;
1101 return TM_ECODE_OK;
1102 }
1103
UnixManagerThreadDeinit(ThreadVars * t,void * data)1104 static TmEcode UnixManagerThreadDeinit(ThreadVars *t, void *data)
1105 {
1106 SCFree(data);
1107 return TM_ECODE_OK;
1108 }
1109
UnixManager(ThreadVars * th_v,void * thread_data)1110 static TmEcode UnixManager(ThreadVars *th_v, void *thread_data)
1111 {
1112 int ret;
1113
1114 /* set the thread name */
1115 SCLogDebug("%s started...", th_v->name);
1116
1117 StatsSetupPrivate(th_v);
1118
1119 /* Set the threads capability */
1120 th_v->cap_flags = 0;
1121 SCDropCaps(th_v);
1122
1123 TmThreadsSetFlag(th_v, THV_INIT_DONE);
1124 while (1) {
1125 ret = UnixMain(&command);
1126 if (ret == 0) {
1127 SCLogError(SC_ERR_FATAL, "Fatal error on unix socket");
1128 }
1129
1130 if ((ret == 0) || (TmThreadsCheckFlag(th_v, THV_KILL))) {
1131 UnixClient *item;
1132 UnixClient *titem;
1133 TAILQ_FOREACH_SAFE(item, &(&command)->clients, next, titem) {
1134 close(item->fd);
1135 SCFree(item);
1136 }
1137 StatsSyncCounters(th_v);
1138 break;
1139 }
1140
1141 UnixCommandBackgroundTasks(&command);
1142 }
1143 return TM_ECODE_OK;
1144 }
1145
1146
1147 /** \brief Spawn the unix socket manager thread
1148 *
1149 * \param mode if set to 1, init failure cause suricata exit
1150 * */
UnixManagerThreadSpawn(int mode)1151 void UnixManagerThreadSpawn(int mode)
1152 {
1153 ThreadVars *tv_unixmgr = NULL;
1154
1155 SCCtrlCondInit(&unix_manager_ctrl_cond, NULL);
1156 SCCtrlMutexInit(&unix_manager_ctrl_mutex, NULL);
1157
1158 tv_unixmgr = TmThreadCreateCmdThreadByName(thread_name_unix_socket,
1159 "UnixManager", 0);
1160
1161 if (tv_unixmgr == NULL) {
1162 FatalError(SC_ERR_FATAL, "TmThreadsCreate failed");
1163 }
1164 if (TmThreadSpawn(tv_unixmgr) != TM_ECODE_OK) {
1165 FatalError(SC_ERR_FATAL, "TmThreadSpawn failed");
1166 }
1167 if (mode == 1) {
1168 if (TmThreadsCheckFlag(tv_unixmgr, THV_RUNNING_DONE)) {
1169 FatalError(SC_ERR_FATAL, "Unix socket init failed");
1170 }
1171 }
1172 return;
1173 }
1174
1175 // TODO can't think of a good name
UnixManagerThreadSpawnNonRunmode(void)1176 void UnixManagerThreadSpawnNonRunmode(void)
1177 {
1178 /* Spawn the unix socket manager thread */
1179 int unix_socket = ConfUnixSocketIsEnable();
1180 if (unix_socket == 1) {
1181 if (UnixManagerInit() == 0) {
1182 UnixManagerRegisterCommand("iface-stat", LiveDeviceIfaceStat, NULL,
1183 UNIX_CMD_TAKE_ARGS);
1184 UnixManagerRegisterCommand("iface-list", LiveDeviceIfaceList, NULL, 0);
1185 UnixManagerRegisterCommand("iface-bypassed-stat",
1186 LiveDeviceGetBypassedStats, NULL, 0);
1187 /* For backward compatibility */
1188 UnixManagerRegisterCommand("ebpf-bypassed-stat",
1189 LiveDeviceGetBypassedStats, NULL, 0);
1190 UnixManagerThreadSpawn(0);
1191 }
1192 }
1193 }
1194
1195 /**
1196 * \brief Used to kill unix manager thread(s).
1197 *
1198 * \todo Kinda hackish since it uses the tv name to identify unix manager
1199 * thread. We need an all weather identification scheme.
1200 */
UnixSocketKillSocketThread(void)1201 void UnixSocketKillSocketThread(void)
1202 {
1203 ThreadVars *tv = NULL;
1204
1205 again:
1206 SCMutexLock(&tv_root_lock);
1207
1208 /* unix manager thread(s) is/are a part of command threads */
1209 tv = tv_root[TVT_CMD];
1210
1211 while (tv != NULL) {
1212 if (strcasecmp(tv->name, "UnixManagerThread") == 0) {
1213 /* If the thread dies during init it will have
1214 * THV_RUNNING_DONE set, so we can set the correct flag
1215 * and exit.
1216 */
1217 if (TmThreadsCheckFlag(tv, THV_RUNNING_DONE)) {
1218 TmThreadsSetFlag(tv, THV_KILL);
1219 TmThreadsSetFlag(tv, THV_DEINIT);
1220 TmThreadsSetFlag(tv, THV_CLOSED);
1221 break;
1222 }
1223 TmThreadsSetFlag(tv, THV_KILL);
1224 TmThreadsSetFlag(tv, THV_DEINIT);
1225 /* Be sure it has shut down */
1226 if (!TmThreadsCheckFlag(tv, THV_CLOSED)) {
1227 SCMutexUnlock(&tv_root_lock);
1228 usleep(100);
1229 goto again;
1230 }
1231 }
1232 tv = tv->next;
1233 }
1234
1235 SCMutexUnlock(&tv_root_lock);
1236 return;
1237 }
1238
1239 #else /* BUILD_UNIX_SOCKET */
1240
UnixManagerThreadSpawn(int mode)1241 void UnixManagerThreadSpawn(int mode)
1242 {
1243 SCLogError(SC_ERR_UNIMPLEMENTED, "Unix socket is not compiled");
1244 return;
1245 }
1246
UnixSocketKillSocketThread(void)1247 void UnixSocketKillSocketThread(void)
1248 {
1249 return;
1250 }
1251
UnixManagerThreadSpawnNonRunmode(void)1252 void UnixManagerThreadSpawnNonRunmode(void)
1253 {
1254 return;
1255 }
1256
1257 #endif /* BUILD_UNIX_SOCKET */
1258
TmModuleUnixManagerRegister(void)1259 void TmModuleUnixManagerRegister (void)
1260 {
1261 #if defined(BUILD_UNIX_SOCKET) && defined(HAVE_SYS_UN_H) && defined(HAVE_SYS_STAT_H) && defined(HAVE_SYS_TYPES_H)
1262 tmm_modules[TMM_UNIXMANAGER].name = "UnixManager";
1263 tmm_modules[TMM_UNIXMANAGER].ThreadInit = UnixManagerThreadInit;
1264 tmm_modules[TMM_UNIXMANAGER].ThreadDeinit = UnixManagerThreadDeinit;
1265 tmm_modules[TMM_UNIXMANAGER].Management = UnixManager;
1266 tmm_modules[TMM_UNIXMANAGER].cap_flags = 0;
1267 tmm_modules[TMM_UNIXMANAGER].flags = TM_FLAG_COMMAND_TM;
1268 #endif /* BUILD_UNIX_SOCKET */
1269 }
1270