blob: 66fd2dac1e1c57ce02167e32bfe68898efd47c52 [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 <unistd.h>
#include <pwd.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/stat.h>
static int fgetline(FILE *f, char *line, size_t len) {
size_t i = 0;
int c;
while((c = fgetc(f)) != '\n') {
if(c == EOF) {
if(!i) return -1;
break;
}
if(i >= len - 1) return -2;
line[i++] = c;
}
line[i] = 0;
return i;
}
static int read_user_info(FILE *f, char **name, char **public_key) {
int i = 0;
char line[4096];
while(1) {
i++;
int len = fgetline(f, line, sizeof line);
if(len == -2) {
int c;
fprintf(stderr, "Warning: line %u in file " USER_LIST_FILE " is too long, skipping\n", i);
while((c = fgetc(f)) != EOF && c != '\n');
continue;
}
if(len < 0) return -1; // EOF
if(len == 0 || *line == '#') continue;
char *p = line;
while(*p && (*p == ' ' || *p == ' ')) p++;
if(!*p) continue;
char *q1 = strchr(p, '"');
if(!q1) {
fprintf(stderr, "Warning: cannot find '\"' at line %u in file " USER_LIST_FILE "\n", i);
continue;
}
*q1 = 0;
if(q1 - p < 8 || strcmp(q1 - 8, "command=") || strchr(p, ' ')) {
fprintf(stderr, "Warning: syntax error in file " USER_LIST_FILE " line %u\n", i);
continue;
}
q1++;
char *q2 = strchr(q1, '"');
if(!q2) {
fprintf(stderr, "Warning: unmatched '\"' in file " USER_LIST_FILE " line %u\n", i);
continue;
}
char *space = strchr(q2 + 1, ' ');
if(!space) {
fprintf(stderr, "Warning: syntax error in file " USER_LIST_FILE " line %u\n", i);
continue;
}
size_t user_name_len = q2 - q1;
if(!user_name_len) {
fprintf(stderr, "Warning: empty user name in file " USER_LIST_FILE " line %u\n", i);
continue;
}
*name = malloc(user_name_len + 1);
if(!*name) {
fprintf(stderr, "Error: allocate %zu bytes failed when processing file " USER_LIST_FILE " line %u\n", user_name_len + 1, i);
return -1;
}
memcpy(*name, q1, user_name_len);
(*name)[user_name_len] = 0;
*public_key = strdup(space + 1);
if(!*public_key) {
fprintf(stderr, "Error: out of memory when processing file " USER_LIST_FILE " line %u\n", i);
return -1;
}
return 0;
}
}
static int adduser_command(int argc, char **argv) {
char *key = NULL;
int force = 0;
while(1) {
int c = getopt(argc, argv, "a:f");
if(c == -1) break;
switch(c) {
case 'a':
key = strdup(optarg);
if(!key) {
perror("strdup");
return 1;
}
break;
case 'f':
force = 1;
break;
case '?':
return -1;
}
}
//fprintf(stderr, "optind = %d, argc = %d\n", optind, argc);
if(argc - optind != 1) {
fputs("Usage: sshoutcfg adduser [-a <public-key>] [-f] <user-name>\n", stderr);
return -1;
}
const char *user = argv[optind];
if(!key) {
key = malloc(4096);
if(!key) {
perror("malloc");
return 1;
}
fprintf(stderr, "Input public key for %s: ", user);
if(fgetline(stdin, key, 4096) == -2) {
free(key);
fputs("Public key too long\n", stderr);
return 1;
}
}
// TODO: verify the key format
FILE *f = fopen(USER_LIST_FILE, "a+");
if(!f) {
perror(USER_LIST_FILE);
free(key);
return 1;
}
int existing_count = 0;
{
char *user_name, *public_key;
while(read_user_info(f, &user_name, &public_key) == 0) {
if(strcmp(key, public_key) == 0) {
free(key);
fprintf(stderr, "This public key is already used by user %s.\n"
"Are you pasted wrong key?\n", user_name);
free(user_name);
free(public_key);
return 1;
}
if(strcmp(user, user_name) == 0) existing_count++;
free(user_name);
free(public_key);
}
}
if(existing_count) {
fprintf(stderr, "%d key%s already exist for user %s\n", existing_count, existing_count > 1 ? "s" : "", user);
if(!force) {
char answer[16];
fprintf(stderr, "Are you sure you want to add this key for user %s? ", user);
do {
int len = fgetline(stdin, answer, sizeof answer);
// Ignore line too long error
if(len == -1 || strncasecmp(answer, "no", 2) == 0 || strncmp(answer, "不", 3) == 0 || strcmp(answer, "否") == 0) {
fputs("Operation canceled\n", stderr);
return 1;
}
} while(strncasecmp(answer, "yes", 3) && strncmp(answer, "是", 3) && strncmp(answer, "好", 3) && strcmp(answer, "可以"));
}
}
if(fprintf(f, "command=\"%s\",no-agent-forwarding,no-port-forwarding %s\n", user, key) < 0) {
perror("fprintf");
free(key);
return 1;
}
free(key);
return -1;
}
static int listuser_command(int argc, char **argv) {
FILE *f = fopen(USER_LIST_FILE, "r");
if(!f) {
perror(USER_LIST_FILE);
return 1;
}
/*
char line[4096];
while(fgetline(f, line, sizeof line) > 0) {
fprintf(stderr, "line = \"%s\"\n", line);
}
*/
char *user_name, *public_key;
while(read_user_info(f, &user_name, &public_key) == 0) {
fprintf(stderr, "User \"%s\", Public key \"%s\"\n", user_name, public_key);
free(user_name);
free(public_key);
}
return 0;
}
static struct subcommand {
const char *name;
int (*func)(int, char **);
} commands[] = {
#define SUBCOMMAND(N) { #N, N##_command }
SUBCOMMAND(adduser),
SUBCOMMAND(listuser),
#undef SUBCOMMAND
{ NULL, NULL }
};
static void print_commands() {
struct subcommand *c = commands;
fputs("Following subcommands are available:\n", stderr);
while(c->name) {
fprintf(stderr, " %s\n", c->name);
c++;
}
}
int main(int argc, char **argv) {
struct passwd *pw = getpwnam("sshout");
if(!pw) {
fputs("sshout user account not exist\n", stderr);
return 1;
}
if(pw->pw_uid == 0) {
fputs("sshout user account have UID 0\n", stderr);
return 1;
}
if(argc < 2) {
print_commands();
return -1;
}
//uid_t myuid = getuid();
uid_t myeuid = geteuid();
if(myeuid == 0) {
if(setreuid(pw->pw_uid, pw->pw_uid) < 0) {
perror("setreuid");
return 1;
}
} else if(myeuid != pw->pw_uid) {
fprintf(stderr, "Current effective UID %u doesn't equal to sshout user account\n", myeuid);
return 1;
}
const char *home = pw->pw_dir;
struct stat st;
if(stat(home, &st) < 0) {
perror(home);
return 1;
}
if(st.st_uid != pw->pw_uid) {
fprintf(stderr, "Home directory '%s' is not owned by sshout (expecting UID=%u, got %u)\n", home, pw->pw_uid, st.st_uid);
return 1;
}
setenv("HOME", home, 1);
if(chdir(home) < 0) {
perror(home);
return 1;
}
struct subcommand *c = commands;
while(c->name) {
if(strcmp(argv[1], c->name) == 0) return c->func(argc - 1, argv + 1);
c++;
}
fprintf(stderr, "Unknown command '%s'\n", argv[1]);
print_commands();
return -1;
}