aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--changelog.txt6
-rw-r--r--docs.txt19
-rw-r--r--laim.c165
3 files changed, 120 insertions, 70 deletions
diff --git a/changelog.txt b/changelog.txt
index 2f9b95c..9255927 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -1,3 +1,9 @@
+2026-04-05 #0.2.6b, #0.2.6b2
+adds expiring mail, with command 'H'
+new, experimental docs.
+index-preserving delete command (ALWAYS DELETE LAST TO FIRST!)
+slightly hardened login paths
+
2026-04-05
#0.2.5b
bug fixes, modified broadcast functionality, ephemeral rooms
diff --git a/docs.txt b/docs.txt
index 267522e..14c3151 100644
--- a/docs.txt
+++ b/docs.txt
@@ -1,14 +1,15 @@
-- news
+
+2026-04-10 #0.3.0-0
+patched a minor path traversal bug, unified lastlogin+ratelimit, prevented DoS on users
+
+
2026-04-09 #0.3.0
adds index fetching, room topics, polling mail, --blim
version header has changed
-2026-04-05 #0.2.6b, #0.2.6b2
-adds expiring mail, with command 'H'
-new, experimental docs.
-index-preserving delete command (ALWAYS DELETE LAST TO FIRST!)
-slightly hardened login paths
+
-- proto
@@ -32,7 +33,7 @@ K mismatch
FIELD TYPES DESCRIPTION
-str printable alphanum + [-_.$ :], newline-terminated
+str printable alphanum + [-_.$ :], newline-terminated, must not start with a dot
num printable u64, newline-terminated
body raw bytes, EOF-terminated, max 65536
csv comma-separated strings, newline-terminated (default max = 16)
@@ -44,9 +45,9 @@ L str:user str:password attempt a login (penalty for wr
COMMANDS ARGS DESCRIPTION RETURN
-G returns all mail mail[]+O
+G returns all mail (mail[]+O)|!
F csv:indexes return mails with given indexes (mail[]+O)|!
-T num:timestamp returns mail after a timestamp mail[]+O
+T num:timestamp returns mail after a timestamp (mail[]+O)|!
S csv:targets body sends mail 7|.|!|O
H csv:targets num:ttl body sends mail with an expiry date (targets, ttl, body) 7|.|!|O
W returns who you are (may get funky) str+\n
@@ -71,7 +72,7 @@ users user database.
mail mail database.
| folder=user
| ~folder=room
-| x/.count=num:index
+| x/.count=bin:index
| x/.login=bin:lastlogin
| x/num=mail
diff --git a/laim.c b/laim.c
index cd491ad..8e1648e 100644
--- a/laim.c
+++ b/laim.c
@@ -1,4 +1,4 @@
-// laim (0.3.0): a lame mail server
+// laim (0.3.0-0): a lame mail server
//
// creator: moon flower fields
// website: coffin.ir
@@ -200,6 +200,10 @@ u64 now() {
return ts.tv_sec;
}
+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); }
+
u64 room_user_count(char *room_path) {
static char members_path[ SZ ];
sit(members_path, room_path, ".members/");
@@ -218,7 +222,6 @@ u64 room_user_count(char *room_path) {
int fd = openat(dir_fd, d->d_name, O_RDONLY);
if (fd >= 0) {
if (flock(fd, LOCK_EX | LOCK_NB) == 0) {
- flock(fd, LOCK_UN);
unlinkat(dir_fd, d->d_name, 0);
} else {
count++;
@@ -269,10 +272,6 @@ void list_users(int dir_fd, bool rooms) {
}
}
-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); \
@@ -330,7 +329,12 @@ void printmail(char path[ SZ ], u64 after) {
return;
}
u64 total = 0;
- read(cfd, &total, sizeof(u64));
+ if (read(cfd, &total, sizeof(u64)) != sizeof(u64)) {
+ close(cfd);
+ close(dir_fd);
+ writes("!");
+ return;
+ }
close(cfd);
for (u64 i = 0; i < total; i++) { printindex(dir_fd, str_u64(i), after); }
@@ -341,7 +345,7 @@ 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));
+ if (read(cfd, &count, sizeof(u64)) != sizeof(u64)) count = 0;
count++;
lseek(cfd, 0, SEEK_SET);
if (write(cfd, &count, sizeof(u64)) != sizeof(u64)) {
@@ -381,6 +385,10 @@ bool field_more = false;
field_more = false; \
for (u8 _i = 0; _i < 64; _i++) { \
int _ch = get_char(); \
+ if (_i == 0 && (char) _ch == '.') { \
+ writes(err); \
+ fail_action; \
+ } \
if (_ch == field_sep) { \
(buf)[ _i ] = 0; \
_term = true; \
@@ -439,29 +447,45 @@ static char member_file[ SZ ] = { 0 };
void leave_room() {
if (member_fd < 0) return;
- flock(member_fd, LOCK_UN);
+ sopen(member_fd);
close(member_fd);
member_fd = -1;
unlink(member_file);
- if (ephemeral && room_user_count((char *) filename) == 0) {
- static char mp2[ SZ ];
- sit(mp2, filename, ".members/");
- *glob_buf = 0;
- rmdir(mp2);
- int cfd2 = open(count_filename, O_RDONLY);
- u64 total2 = 0;
- if (cfd2 >= 0) {
- read(cfd2, &total2, 8);
- close(cfd2);
- }
- for (u64 i = 0; i < total2; i++) {
- static char mp[ SZ ];
- sit(mp, filename, str_u64(i));
- *glob_buf = 0;
- unlink(mp);
+ if (ephemeral) {
+ int room_fd = open(filename, O_RDONLY | O_DIRECTORY);
+ if (room_fd >= 0) {
+ slock(room_fd);
+
+ if (room_user_count((char *) filename) == 0) {
+ // wipe .members/
+ static char mp2[ SZ ];
+ sit(mp2, filename, ".members/");
+ *glob_buf = 0;
+ rmdir(mp2);
+
+ // read .count for mail
+ int cfd2 = open(count_filename, O_RDONLY);
+ u64 total2 = 0;
+ if (cfd2 >= 0) {
+ if (read(cfd2, &total2, 8) == 8) {
+ for (u64 i = 0; i < total2; i++) {
+ static char mp[ SZ ];
+ sit(mp, filename, str_u64(i));
+ *glob_buf = 0;
+ unlink(mp);
+ }
+ }
+ close(cfd2);
+ }
+
+ // cleanup
+ unlink(count_filename);
+ rmdir(filename);
+ }
+
+ sopen(room_fd);
+ close(room_fd);
}
- unlink(count_filename);
- rmdir(filename);
}
ephemeral = false;
}
@@ -641,22 +665,6 @@ laim_start:;
memcopy(user_path + 6, user, userlen);
user_path[ 6 + userlen ] = 0;
- // check lastlogin before sleeping
- char last_login[ SZ ] = { 0 };
- sit(last_login, "mail/", user, "/.login");
- *glob_buf = 0;
- {
- int login_fd = open(last_login, O_RDONLY);
- u64 old_ts = 0, old_pid = 0;
- if (login_fd >= 0) {
- read(login_fd, &old_ts, 8);
- read(login_fd, &old_pid, 8);
- close(login_fd);
- }
- if (old_pid > 0) syscall(62, old_pid, 9); // kill old
- if (now() - old_ts < 5) sleep(1); // fly swatter
- }
-
if (access(user_path, F_OK) != 0) {
writes("I");
sleep(1);
@@ -678,7 +686,36 @@ laim_start:;
}
memzero(actual_pass, 65);
close(fd);
+
writes("O");
+
+ // check lastlogin before sleeping
+ char last_login[ SZ ] = { 0 };
+ sit(last_login, "mail/", user, "/.login");
+ *glob_buf = 0;
+
+ {
+ int login_fd = open(last_login, O_RDWR | O_CREAT, 0600);
+ if (login_fd >= 0) {
+ u64 old_ts = 0, old_pid = 0;
+ read(login_fd, &old_ts, 8);
+ read(login_fd, &old_pid, 8);
+
+ if (old_pid > 0) syscall(62, old_pid, 9); // kill old
+ if (now() - old_ts < 5) sleep(1); // fly swatter
+
+ // atomic replace
+ ftruncate(login_fd, 0);
+ u64 ts = now();
+ u64 pid = syscall(39); // getpid
+ write(login_fd, &ts, 8);
+ write(login_fd, &pid, 8);
+
+ close(login_fd);
+ }
+ // skip on fail
+ }
+
// only fallthrough
} else if (action == EOF) {
return 1;
@@ -689,20 +726,6 @@ laim_start:;
// -- authorized from here on out --
- { // write last login
- char last_login[ SZ ] = { 0 };
- sit(last_login, "mail/", user, "/.login");
- int login_fd = open(last_login, O_RDWR | O_CREAT, 0600);
- if (login_fd >= 0) {
- ftruncate(login_fd, 0);
- u64 ts = now();
- u64 pid = syscall(39); // getpid
- write(login_fd, &ts, 8);
- write(login_fd, &pid, 8);
- close(login_fd);
- }
- }
-
// remove password from memory
memzero(pass, 65);
@@ -903,23 +926,37 @@ laim_start:;
close(ifd);
writes("O");
} else if (c == 'D') {
+ if (member_fd >= 0) {
+ writes("!");
+ goto while_end;
+ }
+
// replaces indexed mail with last mail
// always delete last-to-first
u64 index = scan_u64();
- int dir_fd = open(filename, O_PATH | O_DIRECTORY);
+ int dir_fd = open(filename, O_RDONLY | O_DIRECTORY);
if (dir_fd < 0) err("8");
+ slock(dir_fd);
+
int cfd = openat(dir_fd, ".count", O_RDWR);
if (cfd < 0) {
+ sopen(dir_fd);
close(dir_fd);
err("8");
}
u64 total = 0;
- read(cfd, &total, 8);
+ if (read(cfd, &total, 8) != 8) {
+ close(cfd);
+ sopen(dir_fd);
+ close(dir_fd);
+ err("8");
+ }
if (total == 0 || index >= total) {
close(cfd);
+ sopen(dir_fd);
close(dir_fd);
err("8");
}
@@ -931,8 +968,9 @@ laim_start:;
*glob_buf = 0;
nlod(dst, index);
*glob_buf = 0;
- if (syscall(264, dir_fd, src, dir_fd, dst) < 0) {
+ if (syscall(264, dir_fd, src, dir_fd, dst) < 0) { // renameat2
close(cfd);
+ sopen(dir_fd);
close(dir_fd);
err("8");
}
@@ -941,10 +979,14 @@ laim_start:;
unlinkat(dir_fd, str_u64(last), 0);
lseek(cfd, 0, SEEK_SET);
- write(cfd, &total, 8);
+ if (write(cfd, &total, 8) != 8) {
+ writes("you're out of disk space or something. literally idk how this could happen\n");
+ break;
+ }
ftruncate(cfd, 8);
close(cfd);
+ sopen(dir_fd);
close(dir_fd);
writes("O");
} else if (c == 'C') { // change password
@@ -978,6 +1020,7 @@ laim_start:;
while_end:;
}
+ leave_room();
flush();
return 0;
}