blob: a7e7150031a7f7fbe76e26311d6874f8d4aa6d1b [file] [log] [blame] [raw]
/*
* Copyright 2015-2018 Rivoreo
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
#include "common.h"
#include "client.h"
#include <unistd.h>
#include <string.h>
#include <stdarg.h>
#include <stdlib.h>
#if 0
#include <readline/readline.h>
#include <readline/history.h>
#endif
#include <fcntl.h>
#include <stdio.h>
#include <termios.h>
#include <time.h>
#include <errno.h>
static void print_with_time(time_t t, const char *format, ...) {
va_list ap;
struct tm tm;
if(t == -1) t = time(NULL);
localtime_r(&t, &tm);
printf("\r[%.2d:%.2d:%.2d] ", tm.tm_hour, tm.tm_min, tm.tm_sec);
va_start(ap, format);
vprintf(format, ap);
va_end(ap);
putchar('\n');
}
static void command_who(int fd, int argc, char **argv) {
if(client_send_request_get_online_users(fd) < 0) {
perror("who: write");
}
}
static struct command {
const char *name;
const char *usage;
void (*func)(int, int, char **);
} command_list[] = {
{ "who", "", command_who },
{ "list", "", command_who },
{ NULL, NULL, NULL }
};
static int parse_tokens(char *string, char ***tokens, int length) {
/* Extract whitespace- and quotes- delimited tokens from the given string
and put them into the tokens array. Returns number of tokens
extracted. Length specifies the current size of tokens[].
THIS METHOD MODIFIES string. */
const char * whitespace = " \t\r\n";
char * tokenEnd;
const char * quoteCharacters = "\"\'";
char * end = string + strlen(string);
if(!string) return length;
while(1) {
const char *q;
/* Skip over initial whitespace. */
string += strspn(string, whitespace);
if(!*string) break;
for(q = quoteCharacters; *q; ++q) {
if(*string == *q) break;
}
if(*q) {
/* Token is quoted. */
char quote = *string++;
tokenEnd = strchr(string, quote);
/* If there is no endquote, the token is the rest of the string. */
if(!tokenEnd) tokenEnd = end;
} else {
tokenEnd = string + strcspn(string, whitespace);
}
*tokenEnd = '\0';
{
char **new_tokens;
int newlen = length + 1;
new_tokens = realloc(*tokens, (newlen + 1) * sizeof (char *));
if(!new_tokens) {
/* Out of memory. */
return -1;
}
*tokens = new_tokens;
(*tokens)[length] = string;
length = newlen;
}
if(tokenEnd == end) break;
string = tokenEnd + 1;
}
return length;
}
static void do_command(int fd, const char *command) {
//size_t len = strlen(commnd) + 1;
if(!*command) return;
char **argv = malloc(sizeof(char *));
char *buffer;
if(!argv || !(buffer = strdup(command))) {
print_with_time(-1, "do_command: out of memory");
free(argv);
return;
}
int argc = parse_tokens(buffer, &argv, 0);
if(argc < 0) {
print_with_time(-1, "do_command: out of memory");
free(argv);
free(buffer);
return;
}
struct command *c = command_list;
while(c->name) {
if(strcmp(c->name, argv[0]) == 0) {
c->func(fd, argc, argv);
free(argv);
free(buffer);
return;
}
c++;
}
print_with_time(-1, "Error: Unknown command '%s'", argv[0]);
free(argv);
free(buffer);
}
static void print_online_users(const struct local_online_users_info *info) {
int i = 0;
print_with_time(-1, "your_id = %d", info->your_id);
print_with_time(-1, "count = %d", info->count);
while(i < info->count) {
const struct local_online_user *u = info->user + i++;
printf("%d %s %s %s\n",
u->id, u->user_name, u->host_name, u->id == info->your_id ? "*" : "");
}
}
static void print_message(const struct local_message *msg) {
if(msg->msg_type != SSHOUT_MSG_PLAIN) {
print_with_time(-1, "%s: [Unsupported]", msg->msg_from);
return;
}
char text[msg->msg_length + 1];
memcpy(text, msg->msg, msg->msg_length);
text[msg->msg_length] = 0;
print_with_time(-1, "%s: %s", msg->msg_from, text);
}
static struct termios old;
static void set_terminal() {
tcgetattr(STDIN_FILENO, &old);
struct termios new = old;
new.c_iflag &= ~ICRNL; // Translate carriage return to newline on input
new.c_lflag &= ~ICANON; // Disable buffered i/o
new.c_lflag &= ~ECHO; // Disable echo
tcsetattr(STDIN_FILENO, TCSANOW, &new);
}
static void reset_terminal() {
tcsetattr(STDIN_FILENO, TCSANOW, &old);
}
#if 0
static char **command_completion(const char *text, int start, int end) {
static char *command_names[] = { "/help", NULL };
print_with_time(-1, "function: command_completion(%p<%s>, %d, %d)\n", text, text, start, end);
if(end < 1 || *text != '/') {
rl_bind_key(' ', rl_abort);
return NULL;
}
return command_names;
}
#endif
void client_cli_init_stdin() {
if(isatty(STDIN_FILENO)) set_terminal();
else setvbuf(stdout, NULL, _IOLBF, 0);
//rl_attempted_completion_function = command_completion;
fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK);
}
void client_cli_do_local_packet(int fd) {
struct local_packet *packet;
switch(get_local_packet(fd, &packet)) {
case GET_PACKET_EOF:
print_with_time(-1, "Server closed connection");
close(fd);
if(isatty(STDIN_FILENO)) reset_terminal();
exit(0);
case GET_PACKET_ERROR:
perror("read");
close(fd);
if(isatty(STDIN_FILENO)) reset_terminal();
exit(1);
case GET_PACKET_SHORT_READ:
print_with_time(-1, "Packet short read");
close(fd);
if(isatty(STDIN_FILENO)) reset_terminal();
exit(1);
case GET_PACKET_TOO_LARGE:
print_with_time(-1, "Packet too large");
close(fd);
if(isatty(STDIN_FILENO)) reset_terminal();
exit(1);
case GET_PACKET_OUT_OF_MEMORY:
print_with_time(-1, "Out of memory");
close(fd);
if(isatty(STDIN_FILENO)) reset_terminal();
exit(1);
case 0:
break;
default:
print_with_time(-1, "Internal error");
if(isatty(STDIN_FILENO)) reset_terminal();
abort();
}
switch(packet->type) {
case SSHOUT_LOCAL_STATUS:
break;
case SSHOUT_LOCAL_DISPATCH_MESSAGE:
print_message((struct local_message *)packet->data);
break;
case SSHOUT_LOCAL_ONLINE_USERS_INFO:
print_online_users((struct local_online_users_info *)packet->data);
break;
default:
print_with_time(-1, "Unknown packet type %d", packet->type);
break;
}
free(packet);
}
static void *mem2chr(const void *s, int c1, int c2, size_t n) {
char *p = (void *)s;
unsigned int i = 0;
while(i < n) {
if(p[i] == c1 || p[i] == c2) return p + i;
i++;
}
return NULL;
}
static void *mem3chr(const void *s, int c1, int c2, int c3, size_t n) {
char *p = (void *)s;
unsigned int i = 0;
while(i < n) {
if(p[i] == c1 || p[i] == c2 || p[i] == c3) return p + i;
i++;
}
return NULL;
}
static void do_input_line(int fd, const char *line) {
if(*line == '/') {
do_command(fd, line + 1);
} else if(*line) {
client_post_plain_text_message(fd, GLOBAL_NAME, line);
}
}
static char input_buffer[4906];
static int ss;
// fd is for local packet
void client_cli_do_stdin(int fd) {
#if 0
char *line = readline(NULL);
//char *line = readline("SSHOUT");
if(!line) {
print_with_time(-1, "Exiting ...");
if(isatty(STDIN_FILENO)) reset_terminal();
exit(0);
}
do_input_line(fd, line);
free(line);
#else
int s;
if(ss == sizeof input_buffer) {
char buffer[64];
do {
s = read(STDIN_FILENO, buffer, sizeof buffer);
} while(s < 0 && errno == EINTR);
if(s < 0) {
if(errno == EAGAIN) return;
perror("read");
if(isatty(STDIN_FILENO)) reset_terminal();
exit(1);
}
if(!s) {
print_with_time(-1, "Exiting ...");
if(isatty(STDIN_FILENO)) reset_terminal();
exit(0);
}
char *bs = buffer;
while((bs = mem2chr(bs, '\b', 0x7f, s))) {
ss--;
fputc('\b', stderr);
}
} else {
do {
s = read(STDIN_FILENO, input_buffer + ss, sizeof input_buffer - ss);
} while(s < 0 && errno == EINTR);
if(s < 0) {
if(errno == EAGAIN) return;
perror("read");
if(isatty(STDIN_FILENO)) reset_terminal();
exit(1);
}
if(!s) {
print_with_time(-1, "Exiting ...");
if(isatty(STDIN_FILENO)) reset_terminal();
exit(0);
}
char *br = mem3chr(input_buffer + ss, 0, '\r', '\n', s);
if(br) {
int skip_len = 0;
char *last_br;
do {
if(*br) *br = 0;
br++;
int line_len = br - input_buffer - skip_len;
fputc('\r', stderr);
do_input_line(fd, input_buffer + skip_len);
last_br = br;
br = mem3chr(br, 0, '\r', '\n', s - (br - (input_buffer + ss)));
skip_len += line_len;
} while(br);
ss += s - skip_len;
memmove(input_buffer, last_br, ss);
write(STDERR_FILENO, input_buffer, ss);
} else {
write(STDERR_FILENO, input_buffer + ss, s);
ss += s;
}
}
#endif
}