// laim (0.2.3): a lame mail server
// centeralized design, manual admin sign-up
// files are easily parsable by other programs, and this can act as a sort of IPC as well.
// use --boot to run this program over TCP+TLS
// connect to it with `ncat --ssl
`
// or `nc ` if not running under SSL
//
// stripped using elfkickers' sstrip
// compressed using `upx --ultra-brute laim --lzma --no-align`
//
// created by Moon Flower Fields (https://coffin.ir/)
// or @moonflowerfields on Telegram
// very funny.
#define COMEDIAN false
#define SZ 64 + 5 + 1 + 1
#define EOF -1
#include
#include
#include
#include
#include
#include
typedef uint64_t u64;
typedef uint16_t u16;
typedef uint8_t u8;
typedef u8 byte;
typedef byte bool;
const bool true = 1;
const bool false = 0;
struct linux_dirent64 {
u64 d_ino;
u64 d_off;
u16 d_reclen;
u8 d_type;
char d_name[];
};
char get_char() {
GETCHAR_START:;
char buf[ 1 ];
if (read(STDIN_FILENO, buf, 1) == 0) return EOF;
if (buf[ 0 ] == '\r') goto GETCHAR_START; // band-aid fix.
return buf[ 0 ];
}
char *sadd(char *p, const char *s) {
while (*s) *p++ = *s++;
return p;
}
void memcopy(char *s, const char *d, u64 len) {
for (u64 i = 0; i < len; i++) s[ i ] = d[ i ];
}
void memzero(char* s, u64 len) {
for (u64 i = 0; i < len; i++) s[i] = 0;
}
char *nadd(char *p, u64 n) {
if (!n) {
*p++ = '0';
return p;
}
char tmp[ 20 ];
int i = 20;
while (n) {
tmp[ --i ] = '0' + (n % 10);
n /= 10;
}
int len = 20 - i;
memcopy(p, tmp + i, len);
return p + len;
}
void print_u64(u64 n) {
char tmp[ 20 ];
char *p = nadd(tmp, n);
write(STDOUT_FILENO, tmp, p - tmp);
}
typedef struct {
char buf[ 21 ];
int len;
} u64str;
u64str str_u64(u64 n) {
u64str s;
char *p = nadd(s.buf, n);
*p = 0;
s.len = p - s.buf;
return s;
}
u64 scan_u64() {
u64 n = 0;
char tsbuf[ 21 ];
int len = read(STDIN_FILENO, tsbuf, sizeof(tsbuf) - 1);
for (int i = 0; i < len; i++) {
if (tsbuf[ i ] >= '0' && tsbuf[ i ] <= '9') n = n * 10 + (tsbuf[ i ] - '0');
else
break;
}
return n;
}
u64 lenstr(const char *str) {
u64 i = 0;
while (str[i]) i++;
return i;
}
void writes(const char *string) { write(STDOUT_FILENO, string, lenstr(string)); }
void list_users(int dir_fd) {
char buf[ 1024 ];
int n;
while ((n = syscall(217, dir_fd, buf, sizeof(buf))) > 0) {
for (int off = 0; off < n;) {
struct linux_dirent64 *d = (struct linux_dirent64 *) (buf + off);
if (d->d_name[ 0 ] != '.') {
write(STDOUT_FILENO, d->d_name, lenstr(d->d_name));
char lastlogin[ SZ ] = { 0 };
char *p = lastlogin;
p = sadd(p, "mail/");
p = sadd(p, d->d_name);
p = sadd(p, "/.login");
int login_fd = open(lastlogin, O_RDONLY);
if (login_fd >= 0) {
u64 ts;
read(login_fd, &ts, 8);
writes(" ");
print_u64(ts);
writes("\n");
close(login_fd);
} else {
writes(" 0\n");
}
}
off += d->d_reclen;
}
}
}
int smake(char *name) { return open(name, O_RDWR | O_CREAT, 0600); }
void slock(int fd) { flock(fd, LOCK_EX); }
void sopen(int fd) { flock(fd, LOCK_UN); }
#define err(a) \
({ \
writes(a); \
continue; \
})
void printmail(char path[ SZ ], u64 after) {
mkdir(path, 0700); // make it if it doesnt exist
int dir_fd = open(path, O_RDONLY | O_DIRECTORY);
if (dir_fd < 0) return;
int cfd = openat(dir_fd, ".count", O_RDONLY);
if (cfd < 0) {
writes(";");
close(dir_fd);
return;
}
u64 total = 0;
read(cfd, &total, sizeof(u64));
close(cfd);
for (u64 i = 0; i < total; i++) {
u64str name = str_u64(i);
int mail_fd = openat(dir_fd, name.buf, O_RDONLY);
if (mail_fd < 0) continue;
flock(mail_fd, LOCK_SH);
struct stat st;
fstat(mail_fd, &st);
if (st.st_size == 0) {
close(mail_fd);
continue;
}
u64 ts = 0;
char tsbuf[ 21 ];
int n = read(mail_fd, tsbuf, sizeof(tsbuf) - 1);
if (n <= 0) {
close(mail_fd);
continue;
}
tsbuf[ n ] = 0;
for (int j = 0; j < n; j++) {
if (tsbuf[ j ] >= '0' && tsbuf[ j ] <= '9') ts = ts * 10 + (tsbuf[ j ] - '0');
else {
lseek(mail_fd, j - n, SEEK_CUR);
break;
}
}
if (ts < after) {
close(mail_fd);
continue;
}
char *p, header[ 64 + 1 ] = { 0 };
p = nadd(header, i);
*p++ = '\n';
write(STDOUT_FILENO, header, p - header);
p = nadd(header, ts);
write(STDOUT_FILENO, header, p - header);
char buf[ 256 ];
int r;
while ((r = read(mail_fd, buf, sizeof(buf))) > 0) write(STDOUT_FILENO, buf, r);
writes("\n\neof\n");
close(mail_fd);
}
close(dir_fd);
}
u64 create_index(int dirfd) {
int cfd = openat(dirfd, ".count", O_RDWR | O_CREAT, 0600);
if (cfd < 0) return -1;
u64 count = 0;
read(cfd, &count, sizeof(u64));
count++;
lseek(cfd, 0, SEEK_SET);
if (write(cfd, &count, sizeof(u64)) != sizeof(u64)) {
close(cfd);
return -1;
}
ftruncate(cfd, sizeof(u64));
close(cfd);
return count - 1;
}
const char motd[] = "a laim server!\n";
const char docs[] = {
#embed "docs.txt"
, 0
};
const char source[] = {
#embed "laim.c"
, 0
};
const char makefile[] = {
#embed "Makefile"
,
};
// used for both passwords and usernames.
// not my fault if you wanna use a weird pass.
bool is_path_char(char c) {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '.'
|| c == ' ' || c == '$';
}
#define read_field(buf, err, fail_action, len_buf) \
({ \
bool _term = false; \
for (u8 _i = 0; _i < 64; _i++) { \
(buf)[ _i ] = get_char(); \
if ((buf)[ _i ] == '\n') { \
(buf)[ _i ] = 0; \
_term = true; \
if (len_buf != NULL) { \
u8 *ptr = len_buf; \
*ptr = _i; \
} \
break; \
} \
if (!is_path_char((buf)[ _i ])) { \
writes(err); \
fail_action; \
} \
} \
if (!_term) { \
writes(err); \
fail_action; \
} \
})
// timing safe or whatever.
bool safe_cmp(char *left, char *right, u64 size) {
bool nonmatch = false;
bool ended = false;
for (u64 i = 0; i < size; i++) {
nonmatch = ((*left != *right) && !ended) | nonmatch;
ended = (*left == 0 || *right == 0) | ended;
left++, right++;
}
return !nonmatch;
}
void openwrite(char *filename, const char *content) {
int fd = open(filename, O_RDWR | O_CREAT, 0600);
write(fd, content, lenstr(content));
close(fd);
}
int main(int argc, char **argv) {
char path[ 4096 ] = {0};
readlink("/proc/self/exe", path, sizeof(path) - 1);
if (argc > 1) {
if (safe_cmp(argv[ 1 ], "--help", 6) || safe_cmp(argv[ 1 ], "-h", 2)) {
writes(
"laim: lame mail server\n"
"created by: moon (coffin.ir)\n"
"\n"
"laim is a mail server that operates on STDIO.\n"
"it is meant to be run with a networking program, such as socat, or busybox's tcpsvd.\n"
"it can be served over SSL using socat or busybox's ssl_server, which pipes TLS to I/O.\n"
"\n"
"usage:\n"
" ");
writes(argv[ 0 ]);
writes(
" --help\n"
" shows this message (non-zero exit)\n"
" ");
writes(argv[ 0 ]);
writes(
" --open\n"
" extracts the program's source code and build files (non-zero exit)\n"
" ");
writes(argv[ 0 ]);
writes(
" --boot \n"
" re-runs laim, wrapped with ssl_server and tcpsvd, provided a busybox with those applets is "
"installed.\n"
"\n");
return 1;
} else if (safe_cmp(argv[ 1 ], "--open", 6)) {
openwrite("laim.c", source);
openwrite("docs.txt", docs);
openwrite("Makefile", makefile);
writes("done\n");
return 1;
} else if (safe_cmp(argv[ 1 ], "--boot", 6)) {
if (argc != 5) {
writes("too many / too little args, read --help\n");
return 1;
}
char *new_argv[] = { "tcpsvd", "-v", argv[ 2 ], argv[ 3 ], "ssl_server", "-f", argv[ 4 ], path };
execvp("busybox", new_argv);
} else {
writes("unknown arg\n");
return 1;
}
}
char user[ 64 + 1 ] = { 0 }, pass[ 64 + 1 ] = { 0 };
u8 userlen = 0;
// v=1
writes("laim\2\n");
// manipulates:
// users/ - user db, manually add users
// mail/ - mails directory, each filename is a username
mkdir("users", 0700);
mkdir("mail", 0700);
mkdir("mail/broadcast", 0700);
__BACK_TO_START:;
memzero(user, 65);
memzero(pass, 65);
int action = get_char();
if (action == 'M') {
writes(motd);
goto __BACK_TO_START;
} else if (action == 'L') {
// username
read_field(user, "4", goto __BACK_TO_START, &userlen);
// password
read_field(pass, "5", goto __BACK_TO_START, NULL);
char user_path[ 5 + 1 + 64 + 1 ] = "users/";
memcopy(user_path + 6, user, userlen);
user_path[ 6 + userlen ] = 0;
if (access(user_path, F_OK) != 0) {
writes("N");
goto __BACK_TO_START;
}
int fd = open(user_path, O_RDONLY);
if (fd < 0) {
writes("err:fopen\n");
return 1;
}
char actual_pass[ 65 ] = { 0 };
int pass_size = read(fd, actual_pass, 64);
actual_pass[ pass_size ] = 0;
if (!safe_cmp(pass, actual_pass, 64)) { // safe timing or whatever.
writes("I");
sleep(2); // punish user
goto __BACK_TO_START;
}
memzero(actual_pass, 65);
close(fd);
writes("O");
// only fallthrough
} else if (action == EOF) {
return 1;
} else {
writes("?");
goto __BACK_TO_START;
}
// -- authorized from here on out --
{ // write last login
char last_login[ SZ ] = { 0 };
char *p = last_login;
p = sadd(p, "mail/");
p = sadd(p, user);
p = sadd(p, "/.login");
int login_fd = open(last_login, O_RDWR | O_CREAT, 0600);
if (login_fd >= 0) {
ftruncate(login_fd, 0);
u64 ts = time(NULL);
write(login_fd, &ts, 8);
close(login_fd);
}
}
bool broadcast_mode = false; // kinda useless. may delete. TODO
// remove password from memory
memzero(pass, 65);
// make sure user has a mail dir
char user_filename[ SZ ] = { 0 };
char *p = user_filename;
p = sadd(p, "mail/");
p = sadd(p, user);
p = sadd(p, "/");
mkdir(user_filename, 0700);
char user_count_filename[ SZ ] = { 0 };
p = sadd(user_count_filename, user_filename);
p = sadd(p, ".count");
char broadcast_filename[ SZ ] = "mail/broadcast/";
char broadcast_count_filename[ SZ ] = "mail/broadcast/.count";
char *filename = user_filename;
char *count_filename = user_count_filename;
int _c;
while ((_c = get_char()) != EOF) {
if (_c == '\n') continue;
char c = _c;
if (c == 'G') { // get all mail
printmail(filename, 0);
} else if (c == 'W') { // whoami
if (COMEDIAN) writes("you're... you!");
else { writes(user); }
writes("\n");
} else if (c == 'E') { // count inbox size, includes deleted mails
int count_fd = open(count_filename, O_RDONLY);
if (count_fd < 0) err("!");
u64 result = 0;
int size = read(count_fd, &result, 8);
if (size < 8) {
close(count_fd);
err("!");
}
close(count_fd);
print_u64(result);
writes("\n");
} else if (c == 'B') { // turn on broadcast mode.
broadcast_mode = true;
filename = broadcast_filename;
count_filename = broadcast_count_filename;
writes("O");
} else if (c == 'Q') { // turn off broadcast mode.
broadcast_mode = false;
filename = user_filename;
count_filename = user_count_filename;
writes("O");
} else if (c == 'U') { // list all users
int dir_fd = open("users/", O_RDONLY | O_DIRECTORY);
if (dir_fd < 0) {
writes("err:dir\n");
continue;
}
list_users(dir_fd);
close(dir_fd);
} else if (c == 'T') { // get mail after X
printmail(filename, scan_u64());
} else if (c == 'S') { // send mail
char target[ 64 + 1 ];
read_field(target, "7", goto __WHILE_END, NULL);
char dest_path[ 64 + 5 + 1 + 20 + 1 ];
p = sadd(dest_path, "mail/");
p = sadd(p, target);
p = sadd(p, "/");
mkdir(dest_path, 0700);
// lock directory and calculate size to prevent issues
int dir_fd = open(dest_path, O_RDONLY | O_DIRECTORY);
if (dir_fd < 0) {
writes("err:dir\n");
continue;
}
slock(dir_fd);
u64 index = create_index(dir_fd);
if (index == -1) {
writes("!");
continue;
}
p = sadd(dest_path, "mail/");
p = sadd(p, target);
p = sadd(p, "/");
p = nadd(p, index);
int user_fd = smake(dest_path);
if (user_fd < 0) {
sopen(dir_fd);
close(dir_fd);
writes("err:smake\n");
continue;
}
slock(user_fd);
sopen(dir_fd);
close(dir_fd);
u64 timestamp = time(NULL);
print_u64(timestamp);
writes("\n");
writes(user);
writes("\n");
bool hit_limit = false;
u64 total_size = 0;
char buf[ 256 ];
int read_size;
while ((read_size = read(STDIN_FILENO, buf, 256)) != 0 && read_size > 0) {
total_size += read_size;
write(user_fd, buf, read_size);
if (total_size > 65536) {
hit_limit = true;
writes(".");
break;
}
}
if (!hit_limit) writes("O");
sopen(user_fd);
close(user_fd);
} else if (c == 'D') { // delete mail
u64 index = scan_u64();
char dest_path[ 64 + 5 + 1 + 20 + 1 ];
p = sadd(dest_path, filename);
p = nadd(p, index);
if (unlink(dest_path) < 0) err("8");
else { err("O"); }
} else if (c == 'C') { // change password
char newpass[ 65 ], newpass2[ 65 ];
u8 passlen, passlen2;
read_field(newpass, "6", goto __WHILE_END, &passlen);
read_field(newpass2, "7", goto __WHILE_END, &passlen2);
// passwords don't match
if (passlen != passlen2) err("K");
if (safe_cmp(newpass, newpass2, passlen) != 0) err("K");
char user_path[ 6 + 64 + 1 ] = "users/";
memcopy(user_path + 6, user, userlen);
user_path[ 6 + userlen ] = 0;
int ufd = smake(user_path);
if (ufd < 0) err("8");
slock(ufd);
int newpass_len = lenstr(newpass);
write(ufd, newpass, newpass_len);
ftruncate(ufd, newpass_len); // if newpass_len < oldpass_len
sopen(ufd);
close(ufd);
writes("O");
} else {
writes("?");
}
__WHILE_END:;
}
return 0;
}