diff options
| -rw-r--r-- | changelog.txt | 6 | ||||
| -rw-r--r-- | docs.txt | 19 | ||||
| -rw-r--r-- | laim.c | 165 |
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 @@ -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 @@ -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; } |
