Last active
January 30, 2023 16:09
-
-
Save rlapz/e116e88dbc604c20d002b5bdeab0530a to your computer and use it in GitHub Desktop.
An --ex-- extreme mail server without including any external library (for linux x86_64 only).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: GPL-2.0-only | |
/* ExMail (WIP) | |
* An --ex-- extreme mail server without including any external library | |
* (for linux x86_64 only). | |
* | |
* This simple mail-server aims to implements some of these RFCs: | |
* SMTP: https://datatracker.ietf.org/doc/html/rfc2821 | |
* POP3: https://datatracker.ietf.org/doc/html/rfc1939 | |
* | |
* ---------------------------------------------------------------------- | |
* Compile: | |
* cc -std=c11 -static -ffreestanding -nostdlib -nostartfiles -O3 \ | |
* exmail.c -o exmail; | |
* | |
* Run: [TODO] | |
* SMTP: | |
* ./exmail smtp [listen addr] [listen port] | |
* | |
* POP3 | |
* ./exmail pop3 [listen addr] [listen port] | |
* ---------------------------------------------------------------------- | |
* | |
* Copyright (C) 2022 Ammar Faizi <[email protected]> | |
* Copyright (C) 2023 Arthur Lapz <[email protected]> | |
*/ | |
/* | |
* Bear with me, I want to do some experiments, expect stupid mistake(s). | |
* I can't guarantee this project will be finished. But I will do my best. Yeah! | |
*/ | |
/* Mail dir scheme: | |
* | |
* mailbox/ | |
* +rlapz/ <---------------------------------[ username ] | |
* | +inbox/ <-----------------------------[ received mail dir ] | |
* | | [email protected]/ <----------------[ src username@domain ] | |
* | | | +yyyymmddhhmmss.txt <----------[ mail content ] | |
* | | | +yyyymmddhhmmss.txt | |
* | | [email protected]/ | |
* | | | +yyyymmddhhmmss.txt | |
* | +outbox/ <----------------------------[ sent mail dir ] | |
* | | [email protected]/ <------------[ target username@domain ] | |
* | | | +yyyymmddhhmmss.txt | |
* | +spam/ | |
* | | [email protected]/ | |
* | | | +yyyymmddhhmmss.txt | |
* | +trash/ | |
* | | +stupid_user@spam_enjoyer.com/ | |
* | | | +yyyymmddhhmmss.txt | |
*/ | |
/* Sections: | |
* Entry point | |
* Config | |
* Compiler attributes | |
* Helpers | |
* Constants | |
* Types | |
* Syscall wrappers | |
* Misc. | |
* os | |
* mem | |
* Str | |
* cstr | |
* ascii | |
* fmt | |
* slots | |
* queue | |
* address | |
* socket | |
* DateTime | |
* mysql | |
* --------------------- | |
* TLS | |
* FSDNS | |
* FSCONF | |
* MailBox | |
* POP3 | |
* SMTP | |
* --------------------- | |
* main | |
*/ | |
/* | |
* Entry point: | |
*/ | |
__asm__( | |
".global _start\n" | |
".section .text\n" | |
"_start:\n\t" | |
/* calling stupid_main function */ | |
"popq %rdi\n\t" | |
"movq %rsp, %rsi\n\t" | |
"xorl %ebp, %ebp\n\t" | |
"andq $-16, %rsp\n\t" | |
"movq 0(%rsp), %rdi\n\t" /* argc */ | |
"leaq 8(%rsp), %rsi\n\t" /* argv */ | |
"callq stupid_main\n\t" | |
); | |
/**************************************************************************** | |
* Config * | |
***************************************************************************/ | |
#define MAIL_QUEUE_MAX 1024 | |
#define SMTP_CLIENTS_SIZE 1024 | |
#define POP3_CLIENTS_SIZE 1024 | |
#define SMTP_IO_QUEUE_DEPTH 32 | |
#define POP3_IO_QUEUE_DEPTH 32 | |
#define DEFAULT_CONFIG_FILE "./exmail.fsconf" | |
#define DEFAULT_MAILBOX_DIR "./mailbox" | |
#define DEFAULT_HOST_NAME "localhost" | |
#define DEFAULT_MYSQL_HOST_PORT "127.0.0.1:3306" | |
#define DEFAULT_MYSQL_DB_NAME "exmail" | |
/* | |
* SMTP | |
*/ | |
#define DEFAULT_SMTP_HOST_PORT "127.0.0.1:9002" | |
#define DEFAULT_SMTP_AUTH "PLAIN" | |
/* | |
* POP3 | |
*/ | |
#define DEFAULT_POP3_HOST_PORT "127.0.0.1:9003" | |
/* fsconf file, see: "FSCONF" section */ | |
#define DEFAULT_CONFIG \ | |
"mailbox_dir(" DEFAULT_MAILBOX_DIR ")\n" \ | |
"host_name(" DEFAULT_HOST_NAME ")\n\n" \ | |
"smtp_host_port(" DEFAULT_SMTP_HOST_PORT ")\n" \ | |
"smtp_auth(" DEFAULT_SMTP_AUTH ")\n\n" \ | |
"pop3_host_port(" DEFAULT_POP3_HOST_PORT ")\n\n" \ | |
"mysql_db_name(" DEFAULT_MYSQL_DB_NAME ")\n\n" \ | |
"mysql_host_port(" DEFAULT_MYSQL_HOST_PORT ")\n" | |
/**************************************************************************** | |
* Compiler attributes * | |
***************************************************************************/ | |
#define INLINE __attribute__((__always_inline__)) inline | |
#define NOINLINE __attribute__((__noinline__)) | |
#define COLD __attribute__((__cold__)) | |
#define HOT __attribute__((__hot__)) | |
#define USED __attribute__((__used__)) | |
#define PACKED __attribute__((__packed__)) | |
#define ALIGN(X) __attribute__((__aligned__(X))) | |
#define UNREACHABLE __builtin_unreachable() | |
#define LIKELY(X) __builtin_expect(!!(X), 1) | |
#define UNLIKELY(X) __builtin_expect(!!(X), 0) | |
/**************************************************************************** | |
* Helpers * | |
***************************************************************************/ | |
#define va_start(VA, args) __builtin_va_start(VA, args) | |
#define va_arg(VA, type) __builtin_va_arg(VA, type) | |
#define va_end(VA) __builtin_va_end(VA) | |
#define os_error1(NUM) UNLIKELY(os_error(NUM)) | |
#ifdef NDEBUG | |
#define DPRINT(...) | |
#define PERROR(ERRNO, MSG) \ | |
fmt_printfd(print_buffer_g, PRINT_BUFFER_SIZE, \ | |
STDERR_FILENO, \ | |
"Error: {s}: " MSG ": {s}({u})\n", \ | |
__func__, cstr_error((int)ERRNO), ERRNO) | |
#else | |
#define DPRINT(...) \ | |
fmt_printfd(print_buffer_g, PRINT_BUFFER_SIZE, \ | |
STDOUT_FILENO, __VA_ARGS__) | |
#define PERROR(ERRNO, MSG) \ | |
fmt_printfd(print_buffer_g, PRINT_BUFFER_SIZE, \ | |
STDERR_FILENO, \ | |
"Error: {s}({u}): " MSG ": {s}({u})\n", \ | |
__func__, __LINE__, cstr_error((int)ERRNO), \ | |
ERRNO) | |
#endif | |
/* | |
* Syscalls (linux x86_64) | |
*/ | |
#define SYS0(NUM)({ \ | |
isize rax; \ | |
__asm__ volatile( \ | |
"syscall" \ | |
: "=a"(rax) /* %rax */ \ | |
: "a"((NUM)) /* %rax */ \ | |
: "rcx", "r11", "memory" \ | |
); \ | |
rax; \ | |
}) | |
#define SYS1(NUM, A)({ \ | |
isize rax; \ | |
__asm__ volatile( \ | |
"syscall" \ | |
: "=a"(rax) /* %rax */ \ | |
: "a"((NUM)), /* %rax */ \ | |
"D"((A)) /* %rdi */ \ | |
: "rcx", "r11", "memory" \ | |
); \ | |
rax; \ | |
}) | |
#define SYS2(NUM, A, B)({ \ | |
isize rax; \ | |
__asm__ volatile( \ | |
"syscall" \ | |
: "=a"(rax) /* %rax */ \ | |
: "a"((NUM)), /* %rax */ \ | |
"D"((A)), /* %rdi */ \ | |
"S"((B)) /* %rsi */ \ | |
: "rcx", "r11", "memory" \ | |
); \ | |
rax; \ | |
}) | |
#define SYS3(NUM, A, B, C)({ \ | |
isize rax; \ | |
__asm__ volatile( \ | |
"syscall" \ | |
: "=a"(rax) /* %rax */ \ | |
: "a"((NUM)), /* %rax */ \ | |
"D"((A)), /* %rdi */ \ | |
"S"((B)), /* %rsi */ \ | |
"d"((C)) /* %rdx */ \ | |
: "rcx", "r11", "memory" \ | |
); \ | |
rax; \ | |
}) | |
#define SYS4(NUM, A, B, C, D)({ \ | |
isize rax; \ | |
register __typeof__(D) r10 __asm__("r10") = (D); \ | |
\ | |
__asm__ volatile( \ | |
"syscall" \ | |
: "=a"(rax) /* %rax */ \ | |
: "a"((NUM)), /* %rax */ \ | |
"D"((A)), /* %rdi */ \ | |
"S"((B)), /* %rsi */ \ | |
"d"((C)), /* %rdx */ \ | |
"r"(r10) /* %r10 */ \ | |
: "rcx", "r11", "memory" \ | |
); \ | |
rax; \ | |
}) | |
#define SYS5(NUM, A, B, C, D, E)({ \ | |
isize rax; \ | |
register __typeof__(D) r10 __asm__("r10") = (D); \ | |
register __typeof__(E) r8 __asm__("r8") = (E); \ | |
\ | |
__asm__ volatile( \ | |
"syscall" \ | |
: "=a"(rax) /* %rax */ \ | |
: "a"((NUM)), /* %rax */ \ | |
"D"((A)), /* %rdi */ \ | |
"S"((B)), /* %rsi */ \ | |
"d"((C)), /* %rdx */ \ | |
"r"(r10), /* %r10 */ \ | |
"r"(r8) /* %r8 */ \ | |
: "rcx", "r11", "memory" \ | |
); \ | |
rax; \ | |
}) | |
#define SYS6(NUM, A, B, C, D, E, F)({ \ | |
isize rax; \ | |
register __typeof__(D) r10 __asm__("r10") = (D); \ | |
register __typeof__(E) r8 __asm__("r8") = (E); \ | |
register __typeof__(F) r9 __asm__("r9") = (F); \ | |
\ | |
__asm__ volatile( \ | |
"syscall" \ | |
: "=a"(rax) /* %rax */ \ | |
: "a"((NUM)), /* %rax */ \ | |
"D"((A)), /* %rdi */ \ | |
"S"((B)), /* %rsi */ \ | |
"d"((C)), /* %rdx */ \ | |
"r"(r10), /* %r10 */ \ | |
"r"(r8), /* %r8 */ \ | |
"r"(r9) /* %r9 */ \ | |
: "rcx", "r11", "memory" \ | |
); \ | |
rax; \ | |
}) | |
/**************************************************************************** | |
* Constants * | |
***************************************************************************/ | |
enum { | |
AF_UNSPECT, | |
AF_LOCAL, | |
AF_INET4, | |
AF_INET6 = 10, | |
}; | |
enum { | |
/* TODO */ | |
F_SETF, | |
}; | |
#define INADDR_ANY 0x0 | |
#define INET4_ADDRSTRLEN 16 | |
#define INET6_ADDRSTRLEN 46 | |
enum { | |
IPPROTO_TCP = 6, | |
IPPROTO_UDP = 17, | |
}; | |
enum { | |
MAP_SHARED = 0x1, | |
MAP_PRIVATE = 0x2, | |
MAP_FIXED = 0x10, | |
MAP_ANONYMOUS = 0x20, | |
}; | |
enum { | |
MSG_PEEK = 0x2, | |
MSG_DONTWAIT = 0x40, | |
}; | |
enum { | |
O_RDONLY, | |
O_WRONLY = 01, | |
O_RDWR = 02, | |
O_CREAT = 0100, | |
O_TRUNC = 0200, | |
O_DIRECTORY = 0200000, | |
O_CLOEXEC = 02000000, | |
}; | |
enum { | |
POLL_IN = 0x01, | |
POLL_PRI = 0x02, | |
POLL_OUT = 0x04, | |
POLL_ERR = 0x08, | |
POLL_HUP = 0x10, | |
}; | |
enum { | |
PROT_NONE, | |
PROT_READ, | |
PROT_WRITE, | |
PROT_EXEC = 0x4, | |
}; | |
/* TODO */ | |
enum { | |
SEEK_SET, | |
SEEK_CUR, | |
SEEK_END, | |
}; | |
enum { | |
SHUT_RD, | |
SHUT_WR, | |
SHUT_RDWR, | |
}; | |
enum { | |
SO_REUSEADDR = 2, | |
}; | |
enum { | |
SOCK_STREAM = 1, | |
SOCK_DGRAM, | |
SOCK_RAW, | |
SOCK_NONBLOCK = 04000, | |
}; | |
enum { | |
SOL_SOCKET = 1, | |
}; | |
enum { | |
STDIN_FILENO, | |
STDOUT_FILENO, | |
STDERR_FILENO, | |
}; | |
enum { | |
TCP_NODELAY = 1, | |
TCP_CORK, | |
TCP_QUICKACK = 12, | |
}; | |
/* | |
* Error numbers | |
*/ | |
enum { | |
SUCCESS = 0, | |
EPERM = 1, | |
ENOENT = 2, | |
EINTR = 4, | |
EBADF = 9, | |
EAGAIN = 11, | |
ENOMEM = 12, | |
EACCES = 13, | |
EFAULT = 14, | |
EINVAL = 22, | |
ENOTSOCK = 88, | |
ENOPROTOOPT = 92, | |
EADDRNOTAVAIL = 99, | |
}; | |
/* | |
* Syscall numbers (linux x86_64) | |
* Ref: https://github.com/torvalds/linux/blob/master/arch/x86/entry/syscalls/syscall_64.tbl | |
*/ | |
enum { | |
SYS_READ = 0, | |
SYS_WRITE, | |
SYS_OPEN, | |
SYS_CLOSE, | |
SYS_STAT = 4, | |
SYS_FSTAT = 5, | |
SYS_POLL = 7, | |
SYS_LSEEK, | |
SYS_MMAP, | |
SYS_MUNMAP = 11, | |
SYS_RT_SIGACTION = 13, | |
SYS_IOCTL = 16, | |
SYS_MSYNC = 26, | |
SYS_SOCKET = 41, | |
SYS_CONNECT, | |
SYS_ACCEPT, | |
SYS_SHUTDOWN = 48, | |
SYS_BIND, | |
SYS_LISTEN, | |
SYS_SETSOCKOPT = 54, | |
SYS_FORK = 57, | |
SYS_EXECVE = 59, | |
SYS_EXIT = 60, | |
SYS_MSGSND = 69, /* Nice! */ | |
SYS_FCNTL = 72, | |
SYS_TIME = 201, | |
SYS_GETRANDOM = 318, | |
}; | |
/**************************************************************************** | |
* Types * | |
***************************************************************************/ | |
typedef unsigned long usize; | |
typedef unsigned long long u64; | |
typedef unsigned int u32; | |
typedef unsigned short u16; | |
typedef unsigned char u8; | |
typedef signed long isize; | |
typedef signed long long i64; | |
typedef signed int i32; | |
typedef signed short i16; | |
typedef signed char i8; | |
typedef u32 bool; | |
#define false 0 | |
#define true 1 | |
#define NULL ((void *)0) | |
#define noreturn _Noreturn | |
typedef __builtin_va_list va_list; | |
typedef isize Time; /* time_t */ | |
typedef struct { | |
u64 __val[1024 / (8 * sizeof(u64))]; | |
} SigSet; /* sigset_t */ | |
typedef struct { | |
union { | |
void (*handler)(int); | |
}; | |
SigSet mask; | |
int flags; | |
void (*restorer)(void); | |
} SigAction; /* struct sigaction */ | |
typedef struct { | |
i64 sec; | |
i64 nsec; | |
} TimeSpec; /* timespec */ | |
typedef struct { | |
void *base; | |
usize len; | |
} IOVec; /* struct iovec */ | |
typedef struct { | |
void *name; | |
u32 name_len; | |
IOVec *iov; | |
usize iov_len; | |
void *control; | |
usize control_len; | |
int flags; | |
} MsgHdr; /* struct msghdr */ | |
typedef struct { | |
u32 addr; | |
} Address4; | |
typedef struct { | |
u8 addr[16]; | |
} Address6; | |
typedef struct { | |
u16 family; | |
u8 data[14]; | |
} SockAddress; /* struct sockaddr */ | |
#define SS_MAX_SIZE 128 | |
typedef struct { | |
u16 family ALIGN(8); | |
u8 padding[SS_MAX_SIZE - sizeof(u16)]; | |
} SockAddressStorage; /* struct sockaddr_storage */ | |
typedef struct { | |
u16 family; | |
u16 port; | |
Address4 addr; | |
u8 zero[8]; | |
} SockAddressIn4; /* struct sockaddr_in */ | |
typedef struct { | |
u16 family; | |
u16 port; | |
u32 flow_info; | |
Address6 addr; | |
u32 scope_id; | |
} SockAddressIn6; /* struct sockaddr_in6 */ | |
typedef union { | |
SockAddress any; | |
SockAddressIn4 in4; | |
SockAddressIn6 in6; | |
/* TODO: unix sockaddr */ | |
} Address; /* struct sockaddr */ | |
typedef struct { | |
u64 dev; | |
u64 ino; | |
usize nlink; | |
u32 mode; | |
u32 uid; | |
u32 gid; | |
u32 __pad0; | |
u64 rdev; | |
i64 size; | |
isize blksize; | |
i64 blocks; | |
TimeSpec atime; | |
TimeSpec mtime; | |
TimeSpec ctime; | |
isize __dont_use[3]; | |
} Stat; /* struct stat */ | |
typedef struct { | |
int fd; | |
i16 events; | |
i16 revents; | |
} PollFd; /* struct pollfd */ | |
/*************************************************************************** | |
* Syscall wrappers * | |
**************************************************************************/ | |
static inline isize sys_read(int fd, void *buffer, usize count); | |
static inline isize sys_write(int fd, const void *buffer, usize count); | |
static inline isize sys_open(const char pathname[], int flags, u32 mode); | |
static inline isize sys_close(int fd); | |
static inline isize sys_stat(const char pathname[], Stat *stat); | |
static inline isize sys_fstat(int fd, Stat *stat); | |
static inline isize sys_poll(PollFd *pfds, usize size, i32 timeout); | |
static inline isize sys_lseek(int fd, i64 offset, int whence); | |
static inline isize sys_mmap(void *addr, usize len, int prot, int flags, | |
int fd, i64 offt); | |
static inline isize sys_munmap(void *addr, usize len); | |
static inline isize sys_sigaction(int num, const SigAction *act, | |
SigAction *old); | |
static inline isize sys_ioctl(int fd, u32 request, usize arg); | |
static inline isize sys_msync(void *addr, usize len, int flags); | |
static inline isize sys_socket(int domain, int type, int proto); | |
static inline isize sys_connect(int fd, const SockAddress *addr, u32 len); | |
static inline isize sys_accept(int fd, SockAddress *restrict addr, | |
u32 *restrict len); | |
static inline isize sys_shutdown(int fd, u32 how); | |
static inline isize sys_bind(int fd, const SockAddress *addr, u32 len); | |
static inline isize sys_listen(int fd, int backlog); | |
static inline isize sys_setsockopt(int fd, int level, int optname, | |
const void *optval, u32 len); | |
static inline isize sys_fork(void); | |
static inline isize sys_execve(const char pathname[], char *const argv[], | |
char *const envp[]); | |
static inline isize sys_exit(int ret); | |
static inline isize sys_fcntl(int fd, int cmd, usize arg); | |
static inline isize sys_time(Time *time); | |
static inline isize sys_getrandom(void *buffer, usize len, u32 flags); | |
/*************************************************************************** | |
* Misc. * | |
***************************************************************************/ | |
/* | |
* os | |
*/ | |
/* only for sys_* function */ | |
static inline bool os_error(isize num); | |
/* | |
* mem | |
*/ | |
typedef struct { | |
usize size; | |
u8 mem[]; | |
} MemHeap; | |
static int mem_alloc(void **dest, usize size); | |
static void mem_free(void *mem); | |
static void mem_copy(void *dest, const void *src, usize size); | |
static void mem_set(void *dest, u8 c, usize size); | |
static inline u16 mem_beton16(u16 num); | |
static inline u16 mem_ntobe16(u16 num); | |
static inline u32 mem_beton32(u32 num); | |
static inline u32 mem_ntobe32(u32 num); | |
/* | |
* Str | |
* | |
* size: buffer size including '\0' byte | |
*/ | |
typedef struct { | |
bool is_heap; | |
usize len; | |
usize size; | |
char *cstr; | |
} Str; | |
static void str_init(Str *self, char buffer[], usize size); | |
static int str_init_alloc(Str *self, usize size); | |
static void str_deinit(Str *self); | |
static const char *str_append(Str *self, const char src[]); | |
static const char *str_fmt_append(Str *self, const char fmt[], ...); | |
static int str_shrink(Str *self, usize size); | |
/* | |
* cstr | |
* | |
* NOTE: | |
* @size: size of `buffer` | |
* @*size: size of `buffer`, and will returns the length of "written" bytes | |
* excluding '\0' | |
* ret: int: error: returns <0 otherwise success | |
* ptr: error: returns NULL otherwise success | |
*/ | |
static void cstr_copy(char dest[], usize size, const char src[]); | |
static int cstr_copy1(char dest[], usize *size, const char src[]); | |
static usize cstr_len(const char cstr[]); | |
static inline void cstr_printc(char c); | |
static void cstr_print(const char cstr[]); | |
static const char *cstr_find_char(const char cstr[], u8 c); | |
static const char *cstr_find(const char hy[], const char nd[]); | |
static bool cstr_eql(const char cstr1[], const char cstr2[]); | |
static bool cstr_eql1(const char cstr1[], const char cstr2[], | |
usize len); | |
static void cstr_reverse(char cstr[]); | |
static int cstr_to_u64(const char cstr[], u64 *ret); | |
static int cstr_to_i64(const char cstr[], i64 *ret); | |
static int cstr_from_u64(char buffer[], usize *size, u64 num); | |
static int cstr_from_i64(char buffer[], usize *size, i64 num); | |
COLD static const char *cstr_error(int errnum); | |
/* | |
* ascii | |
*/ | |
static inline bool ascii_chk(u8 c); | |
static inline u8 ascii_from(u8 c); | |
static inline bool ascii_isspace(u8 c); | |
static inline bool ascii_isdigit(u8 c); | |
static inline bool ascii_isxdigit(u8 c); | |
static inline bool ascii_islower(u8 c); | |
static inline bool ascii_isupper(u8 c); | |
static inline u8 ascii_tolower(u8 c); | |
static inline u8 ascii_toupper(u8 c); | |
/* | |
* fmt: a simple and footgunny string formatter | |
* (these simple implementations does not perform strict exceptions nor | |
* support escape character) | |
* | |
* format arguments: | |
* {i} => signed number (int) | |
* {u} => unsigned number (unsigned int) | |
* {s} => string | |
* {c} => char | |
* | |
* example: | |
* fmt_printfd(buffer, 120, STDOUT_FILENO, "{s}: {u}\n", "num", 123); | |
* result: | |
* "num: 123\n" | |
* | |
* NOTE: | |
* size: | |
* input : buffer length including '\0' | |
* output: returns "written" bytes excluding '\0' | |
* ret: returns 0 on sucess, <0 on error | |
*/ | |
static int fmt_formatv(char buffer[], usize *size, const char fmt[], | |
va_list va); | |
static void fmt_printfd(char buffer[], usize size, int fd, | |
const char fmt[], ...); | |
static int fmt_format(char buffer[], usize *size, const char fmt[], ...); | |
/* | |
* slots | |
*/ | |
typedef struct { | |
u32 count; | |
u32 size; | |
u32 *array; | |
} Slots; | |
static int slots_init(Slots *self, u32 size); | |
static void slots_deinit(Slots *self); | |
static inline int slots_push(Slots *self, u32 udata); | |
static inline i64 slots_pop(Slots *self); | |
/* | |
* queue; ring | |
*/ | |
typedef struct { | |
u32 head; | |
u32 tail; | |
u32 count; | |
u32 nmemb; | |
u32 size; | |
void *array; | |
} Queue; | |
static int queue_init(Queue *self, u32 nmemb, u32 size); | |
static void queue_deinit(Queue *self); | |
static inline void *queue_sqe(Queue *self); | |
static inline void *queue_peek(Queue *self); | |
static inline void queue_seen(Queue *self); | |
/* | |
* address | |
*/ | |
static int address4_from(Address4 *self, const char addr[]); | |
static int address6_from(Address6 *self, const char addr[]); | |
static int address4_to(const Address4 *self, char buf[INET4_ADDRSTRLEN]); | |
static int address6_to(const Address6 *self, char buf[INET6_ADDRSTRLEN]); | |
/* | |
* socket | |
*/ | |
static int socket_listener(const char addr[], u16 port); | |
/* | |
* DateTime | |
*/ | |
typedef struct { | |
u8 month; | |
u8 day; | |
u8 hour; | |
u8 minute; | |
u8 second; | |
u16 year; | |
} DateTime; | |
static int datetime_get(DateTime *self); | |
/* | |
* mysql | |
*/ | |
#define MYSQL_ARGS_COUNT 32 | |
typedef struct { | |
char host[64]; | |
char port[6]; | |
char name[256]; | |
char uname[256]; | |
char passwd[256]; | |
u32 args_len; | |
char *args[MYSQL_ARGS_COUNT]; | |
} MySQL; | |
static void mysql_init(MySQL *self, const char host[], const char port[], | |
const char name[], const char uname[], const char passwd[]); | |
static int mysql_prep(MySQL *self, u32 count, ...); | |
static int mysql_exec(MySQL *self); | |
/**************************************************************************** | |
* TLS * | |
***************************************************************************/ | |
/**************************************************************************** | |
* FSDNS * | |
***************************************************************************/ | |
/* A f*cking simple DNS resolver */ | |
typedef struct { | |
} FsDns; | |
static int fsdns_resolve(const char addr[], const char port[]); | |
/*************************************************************************** | |
* FSCONF * | |
**************************************************************************/ | |
/* A f*cking simple, error prone, and slow key-value-based file configuration | |
* | |
* File name: exmail.fsconf | |
* ------------------------ | |
* key0(value)\n | |
* key1(value)\n | |
* ------------------------ | |
* | |
* Example: | |
* ------------------------ | |
* mailbox_dir(mailbox)\n | |
* smtp_host_port(127.0.0.1:9002)\n | |
* pop3_host_port(127.0.0.1:9003)\n | |
* mysql_host_port(127.0.0.1:9004)\n | |
* ------------------------ | |
* NOTE: '\n' is a must! | |
*/ | |
static int fsconf_get(char dest[], usize size, const char src[], | |
const char key[]); | |
/**************************************************************************** | |
* MailBox * | |
***************************************************************************/ | |
typedef enum { | |
MAILBOX_INBOX, | |
MAILBOX_OUTBOX, | |
MAILBOX_SPAM, | |
MAILBOX_TRASH, | |
} MailBox; | |
/* | |
* ud: username@domain | |
*/ | |
static int mailbox_add(MailBox self, const char ud[], const char mail[], | |
usize len); | |
static int mailbox_get(MailBox self, const char ud[], char mail[], usize size); | |
/**************************************************************************** | |
* POP3 * | |
***************************************************************************/ | |
typedef struct { | |
} Pop3Packet; | |
typedef struct { | |
Pop3Packet packet; | |
} Pop3Client; | |
typedef struct { | |
bool is_alive; | |
int sock_fd; | |
Slots client_slots; | |
usize clients_count; | |
Pop3Client clients[POP3_CLIENTS_SIZE]; | |
} Pop3; | |
static int pop3_init(Pop3 *self, const char addr[], const char port[]); | |
static void pop3_deinit(Pop3 *self); | |
static int pop3_run(Pop3 *self); | |
static void pop3_stop(Pop3 *self); | |
/**************************************************************************** | |
* SMTP * | |
***************************************************************************/ | |
typedef struct { | |
} SmtpPacket; | |
typedef struct { | |
SmtpPacket packet; | |
} SmtpClient; | |
typedef struct { | |
bool is_alive; | |
int dns_fd; | |
int sock_fd; | |
Slots client_slots; | |
usize clients_count; | |
SmtpClient clients[SMTP_CLIENTS_SIZE]; | |
} Smtp; | |
static int smtp_init(Smtp *self, const char addr[], const char port[]); | |
static void smtp_deinit(Smtp *self); | |
static int smtp_run(Smtp *self); | |
static void smtp_stop(Smtp *self); | |
/*************************************************************************** | |
* Global variables * | |
**************************************************************************/ | |
#define PRINT_BUFFER_SIZE 8192 | |
static char print_buffer_g[PRINT_BUFFER_SIZE]; | |
static const char *mailbox_dir_g = DEFAULT_MAILBOX_DIR; | |
/* | |
############################################################################ | |
############################################################################ | |
# IMPLEMENTATIONS # | |
############################################################################ | |
############################################################################ | |
*/ | |
/*************************************************************************** | |
* Syscall wrappers * | |
**************************************************************************/ | |
static inline isize | |
sys_read(int fd, void *buffer, usize count) | |
{ | |
return SYS3(SYS_READ, fd, buffer, count); | |
} | |
static inline isize | |
sys_write(int fd, const void *buffer, usize count) | |
{ | |
return SYS3(SYS_WRITE, fd, buffer, count); | |
} | |
static inline isize | |
sys_open(const char pathname[], int flags, u32 mode) | |
{ | |
return SYS3(SYS_OPEN, pathname, flags, mode); | |
} | |
static inline isize | |
sys_close(int fd) | |
{ | |
return SYS1(SYS_CLOSE, fd); | |
} | |
static inline isize | |
sys_stat(const char pathname[], Stat *stat) | |
{ | |
return SYS2(SYS_STAT, pathname, stat); | |
} | |
static inline isize | |
sys_fstat(int fd, Stat *stat) | |
{ | |
return SYS2(SYS_FSTAT, fd, stat); | |
} | |
static inline isize | |
sys_poll(PollFd *pfds, usize size, i32 timeout) | |
{ | |
return SYS3(SYS_POLL, pfds, size, timeout); | |
} | |
static inline isize | |
sys_lseek(int fd, i64 offset, int whence) | |
{ | |
return SYS3(SYS_LSEEK, fd, offset, whence); | |
} | |
static inline isize | |
sys_mmap(void *addr, usize len, int prot, int flags, int fd, i64 offt) | |
{ | |
return SYS6(SYS_MMAP, addr, len, prot, flags, fd, offt); | |
} | |
static inline isize | |
sys_munmap(void *addr, usize len) | |
{ | |
return SYS2(SYS_MUNMAP, addr, len); | |
} | |
static inline isize | |
sys_sigaction(int num, const SigAction *act, SigAction *old) | |
{ | |
return SYS3(SYS_RT_SIGACTION, num, act, old); | |
} | |
static inline isize | |
sys_ioctl(int fd, u32 request, usize arg) | |
{ | |
return SYS3(SYS_IOCTL, fd, request, arg); | |
} | |
static inline isize | |
sys_msync(void *addr, usize len, int flags) | |
{ | |
return SYS3(SYS_MSYNC, addr, len, flags); | |
} | |
static inline isize | |
sys_socket(int domain, int type, int proto) | |
{ | |
return SYS3(SYS_SOCKET, domain, type, proto); | |
} | |
static inline isize | |
sys_connect(int fd, const SockAddress *addr, u32 len) | |
{ | |
return SYS3(SYS_CONNECT, fd, addr, len); | |
} | |
static inline isize | |
sys_accept(int fd, SockAddress *restrict addr, u32 *restrict len) | |
{ | |
return SYS3(SYS_ACCEPT, fd, addr, len); | |
} | |
static inline isize | |
sys_shutdown(int fd, u32 how) | |
{ | |
return SYS2(SYS_SHUTDOWN, fd, how); | |
} | |
static inline isize | |
sys_bind(int fd, const SockAddress *addr, u32 len) | |
{ | |
return SYS3(SYS_BIND, fd, addr, len); | |
} | |
static inline isize | |
sys_listen(int fd, int backlog) | |
{ | |
return SYS2(SYS_LISTEN, fd, backlog); | |
} | |
static inline isize | |
sys_setsockopt(int fd, int level, int optname, const void *optval, u32 len) | |
{ | |
return SYS5(SYS_SETSOCKOPT, fd, level, optname, optval, len); | |
} | |
static inline isize | |
sys_fork(void) | |
{ | |
return SYS0(SYS_FORK); | |
} | |
static inline isize | |
sys_execve(const char pathname[], char *const argv[], char *const envp[]) | |
{ | |
return SYS3(SYS_EXECVE, pathname, argv, envp); | |
} | |
static inline isize | |
sys_exit(int ret) | |
{ | |
return SYS1(SYS_EXIT, ret); | |
} | |
static inline isize | |
sys_fcntl(int fd, int cmd, usize arg) | |
{ | |
return SYS3(SYS_FCNTL, fd, cmd, arg); | |
} | |
static inline isize | |
sys_time(Time *time) | |
{ | |
return SYS1(SYS_TIME, time); | |
} | |
static inline isize | |
sys_getrandom(void *buffer, usize len, u32 flags) | |
{ | |
return SYS3(SYS_GETRANDOM, buffer, len, flags); | |
} | |
/*************************************************************************** | |
* Misc. * | |
***************************************************************************/ | |
/* | |
* os | |
*/ | |
static INLINE bool | |
os_error(isize num) | |
{ | |
return (num > -4096 && num < 0); | |
} | |
/* | |
* mem | |
*/ | |
static int | |
mem_alloc(void **dest, usize size) | |
{ | |
const usize new_size = sizeof(MemHeap) + size; | |
const isize mem = sys_mmap(NULL, new_size, PROT_READ | PROT_WRITE, | |
MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); | |
if (os_error1(mem)) | |
return (int)mem; | |
MemHeap *const heap = (MemHeap *)mem; | |
heap->size = new_size; | |
*dest = heap->mem; | |
return 0; | |
} | |
#define mem_alloc1(dest, size) mem_alloc((void **)dest, size) | |
static void | |
mem_free(void *mem) | |
{ | |
MemHeap *const heap = (MemHeap *)(((u8 *)mem) - sizeof(MemHeap)); | |
const usize size = heap->size; | |
(void)sys_munmap(heap, size); | |
} | |
static void | |
mem_copy(void *dest, const void *src, usize size) | |
{ | |
u8 *const dest_ = (u8 *)dest; | |
const u8 *const src_ = (const u8 *)src; | |
for (usize i = 0; i < size; i++) | |
dest_[i] = src_[i]; | |
} | |
static void | |
mem_set(void *dest, u8 c, usize size) | |
{ | |
u8 *const dest_ = (u8 *)dest; | |
while (size--) | |
dest_[size] = c; | |
} | |
static inline u16 | |
mem_beton16(u16 num) | |
{ | |
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ | |
return __builtin_bswap16(num); | |
#else | |
return num; | |
#endif | |
} | |
static inline u16 | |
mem_ntobe16(u16 num) | |
{ | |
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ | |
return __builtin_bswap16(num); | |
#else | |
return num; | |
#endif | |
} | |
static inline u32 | |
mem_beton32(u32 num) | |
{ | |
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ | |
return __builtin_bswap32(num); | |
#else | |
return num; | |
#endif | |
} | |
static inline u32 | |
mem_ntobe32(u32 num) | |
{ | |
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ | |
return __builtin_bswap32(num); | |
#else | |
return num; | |
#endif | |
} | |
/* | |
* Str | |
*/ | |
static void | |
str_init(Str *self, char buffer[], usize size) | |
{ | |
self->len = 0; | |
self->size = size; | |
self->cstr = buffer; | |
self->is_heap = false; | |
} | |
static int | |
str_init_alloc(Str *self, usize size) | |
{ | |
str_init(self, NULL, size); | |
const int ret = mem_alloc1(&self->cstr, size); | |
if (ret < 0) | |
return ret; | |
self->is_heap = true; | |
return 0; | |
} | |
static void | |
str_deinit(Str *self) | |
{ | |
if (self->is_heap) | |
mem_free(self->cstr); | |
self->len = 0; | |
} | |
static const char * | |
str_append(Str *self, const char src[]) | |
{ | |
char *const cstr = self->cstr; | |
const usize size = self->size; | |
const usize tmp_len = self->len; | |
usize len = tmp_len; | |
while ((len < size) && (*src != '\0')) | |
cstr[len++] = *src++; | |
if (*src != '\0') { | |
cstr[tmp_len] = '\0'; | |
return NULL; | |
} | |
cstr[len] = '\0'; | |
self->len = len; | |
return cstr; | |
} | |
static const char * | |
str_fmt_append(Str *self, const char fmt[], ...) | |
{ | |
int ret = -1; | |
va_list va; | |
const usize size = self->size; | |
usize len = self->len; | |
usize w_len = size - len; | |
if (len < size) { | |
va_start(va, fmt); | |
ret = fmt_formatv(self->cstr + len, &w_len, fmt, va); | |
va_end(va); | |
} | |
if (ret < 0) | |
return NULL; | |
self->len += w_len; | |
return self->cstr; | |
} | |
static int | |
str_shrink(Str *self, usize size) | |
{ | |
usize len = self->len; | |
if (size <= len) { | |
len -= size; | |
self->len = len; | |
self->cstr[len] = '\0'; | |
return 0; | |
} | |
return -EINVAL; | |
} | |
/* | |
* cstr | |
*/ | |
static void | |
cstr_copy(char dest[], usize size, const char src[]) | |
{ | |
usize i = 0; | |
for (; (i < size && *src != '\0'); i++, src++) | |
dest[i] = *src; | |
dest[i] = '\0'; | |
} | |
static int | |
cstr_copy1(char dest[], usize *size, const char src[]) | |
{ | |
usize size_ = *size; | |
if (size_ == 0) | |
return -ENOMEM; | |
size_--; | |
usize i = 0; | |
for (; (i < size_ && *src != '\0'); i++, src++) | |
dest[i] = *src; | |
dest[i] = '\0'; | |
*size = i; | |
if (*src == '\0') | |
return 0; | |
return -ENOMEM; | |
} | |
static usize | |
cstr_len(const char cstr[]) | |
{ | |
usize len = 0; | |
while (cstr[len] != '\0') | |
len++; | |
return len; | |
} | |
static inline void | |
cstr_printc(char c) | |
{ | |
(void)sys_write(STDOUT_FILENO, &c, 1); | |
} | |
static void | |
cstr_print(const char cstr[]) | |
{ | |
const usize len = cstr_len(cstr); | |
usize wrt = 0; | |
while (wrt < len) { | |
isize wr = sys_write(STDOUT_FILENO, cstr + wrt, len - wrt); | |
if (os_error1(wr)) | |
break; | |
wrt += (usize)wr; | |
} | |
} | |
static const char * | |
cstr_find_char(const char cstr[], u8 c) | |
{ | |
for (; *cstr != '\0'; cstr++) { | |
if (*cstr == c) | |
return cstr; | |
} | |
return NULL; | |
} | |
static const char * | |
cstr_find(const char hy[], const char nd[]) | |
{ | |
const char *ret = NULL; | |
const char *tmp = nd; | |
usize found = 0; | |
for (; (*hy != '\0') && (*tmp != '\0'); hy++) { | |
if (*hy != *tmp) { | |
tmp = nd; | |
found = 0; | |
continue; | |
} | |
found++; | |
if (found == 1) | |
ret = hy; | |
tmp++; | |
} | |
if (((tmp - nd) == found) && (*tmp == '\0')) | |
return ret; | |
return NULL; | |
} | |
static bool | |
cstr_eql(const char cstr1[], const char cstr2[]) | |
{ | |
while ((*cstr1 != '\0') && (*cstr2 != '\0')) { | |
if (*cstr1 != *cstr2) | |
return false; | |
cstr1++; | |
cstr2++; | |
} | |
/* both should have the same length */ | |
if ((*cstr1 == '\0') && (*cstr2 == '\0')) | |
return true; | |
return false; | |
} | |
static bool | |
cstr_eql1(const char cstr1[], const char cstr2[], usize len) | |
{ | |
while ((len != 0) && (*cstr1 != '\0') && (*cstr2 != '\0')) { | |
if (*cstr1 != *cstr2) | |
return false; | |
cstr1++; | |
cstr2++; | |
len--; | |
} | |
if (len != 0) | |
return false; | |
return true; | |
} | |
static void | |
cstr_reverse(char cstr[]) | |
{ | |
if (*cstr == '\0') | |
return; | |
char tmp; | |
usize len = cstr_len(cstr) -1; | |
for (usize i = 0; i < len; i++, len--) { | |
tmp = cstr[i]; | |
cstr[i] = cstr[len]; | |
cstr[len] = tmp; | |
} | |
} | |
static int | |
cstr_to_u64(const char cstr[], u64 *ret) | |
{ | |
if (*cstr == '\0') | |
return -EINVAL; | |
u64 tmp = 0; | |
for (; *cstr != '\0'; cstr++) { | |
if (ascii_isdigit(*cstr)) { | |
tmp = (10 * tmp) + (*cstr - '0'); | |
continue; | |
} | |
break; | |
} | |
if (*cstr != '\0') | |
return -EINVAL; | |
*ret = tmp; | |
return 0; | |
} | |
static int | |
cstr_to_i64(const char cstr[], i64 *ret) | |
{ | |
if (*cstr == '\0') | |
return -EINVAL; | |
u64 tmp; | |
bool has_sign = false; | |
if (*cstr == '-') { | |
has_sign = true; | |
cstr++; | |
if (*cstr == '\0') | |
return -EINVAL; | |
} | |
const int ret_ = cstr_to_u64(cstr, &tmp); | |
if (ret_ < 0) | |
return ret_; | |
i64 tmp_ = (i64)tmp; | |
if (has_sign) | |
tmp_ = -tmp_; | |
*ret = tmp_; | |
return 0; | |
} | |
static int | |
cstr_from_u64(char buffer[], usize *size, u64 num) | |
{ | |
usize size_ = *size; | |
if (size_ < 2) | |
return -ENOMEM; | |
size_--; | |
u64 res; | |
usize i = 0; | |
do { | |
res = num % 10; | |
buffer[i++] = '0' + res; | |
num /= 10; | |
} while ((i < size_) && (num != 0)); | |
if (num != 0) | |
return -ENOMEM; | |
buffer[i] = '\0'; | |
cstr_reverse(buffer); | |
*size = i; | |
return 0; | |
} | |
static int | |
cstr_from_i64(char buffer[], usize *size, i64 num) | |
{ | |
usize size_ = *size; | |
if (size_ < 2) | |
return -ENOMEM; | |
u64 tmp; | |
bool has_sign = false; | |
if (num < 0) { | |
*buffer = '-'; | |
buffer++; | |
size_--; | |
tmp = (u64)-num; | |
has_sign = true; | |
} else { | |
tmp = (u64)num; | |
} | |
const int ret = cstr_from_u64(buffer, &size_, tmp); | |
if (ret < 0) | |
return ret; | |
if (has_sign) | |
size_++; | |
*size = size_; | |
return 0; | |
} | |
COLD static const char * | |
cstr_error(int errnum) | |
{ | |
switch (errnum) { | |
case SUCCESS: return "Success"; | |
case EPERM: return "Operation not permitted"; | |
case ENOENT: return "No such file or directory"; | |
case EINTR: return "Interrupted system call"; | |
case EBADF: return "Bad file descriptor"; | |
case EAGAIN: return "Resource temporarily unavailable"; | |
case ENOMEM: return "Cannot allocate memory"; | |
case EACCES: return "Permission denied"; | |
case EFAULT: return "Bad request"; | |
case EINVAL: return "Invalid argument"; | |
case ENOTSOCK: return "Socket operation on non-socket"; | |
case ENOPROTOOPT: return "Protocol not available"; | |
case EADDRNOTAVAIL: return "Cannot assign requested address"; | |
} | |
return "Unknown error"; | |
} | |
/* | |
* ascii | |
*/ | |
static inline bool | |
ascii_chk(u8 c) | |
{ | |
return (c < 128); | |
} | |
static inline u8 | |
ascii_from(u8 c) | |
{ | |
return (c & 127); | |
} | |
static inline bool | |
ascii_isspace(u8 c) | |
{ | |
return (c == ' '); | |
} | |
static inline bool | |
ascii_isdigit(u8 c) | |
{ | |
return (c >= '0' && c <= '9'); | |
} | |
static inline bool | |
ascii_isxdigit(u8 c) | |
{ | |
return (ascii_isdigit(c) || (c >= 'a' && c <= 'f') || | |
(c >= 'A' && c <= 'F')); | |
} | |
static inline bool | |
ascii_islower(u8 c) | |
{ | |
return (c >= 'a' && c <= 'z'); | |
} | |
static inline bool | |
ascii_isupper(u8 c) | |
{ | |
return (c >= 'A' && c <= 'Z'); | |
} | |
static inline u8 | |
ascii_tolower(u8 c) | |
{ | |
if (ascii_isupper(c)) | |
return (c | 32); /* 0b00100000 */ | |
return c; | |
} | |
static inline u8 | |
ascii_toupper(u8 c) | |
{ | |
if (ascii_islower(c)) | |
return (c & 223); /* 0b11011111 */ | |
return c; | |
} | |
/* | |
* fmt | |
*/ | |
/* TODO: simplify and hardening */ | |
static int | |
fmt_formatv(char buffer[], usize *size, const char fmt[], va_list va) | |
{ | |
bool has_open_tok = false; | |
bool find_close_tok = false; | |
char tok_type = 0; | |
usize i = 0; | |
usize size_ = *size; | |
if (size_ < 2) | |
return -ENOMEM; | |
/* reserving 1 byte for '\0' */ | |
size_--; | |
for (; (*fmt != '\0') && (i < size_); fmt++) { | |
if (*fmt == '{' && (!has_open_tok)) { | |
has_open_tok = true; | |
continue; | |
} | |
if (has_open_tok && (!find_close_tok)) { | |
tok_type = *fmt; | |
find_close_tok = true; | |
continue; | |
} | |
if (find_close_tok) { | |
has_open_tok = false; | |
find_close_tok = false; | |
/* size + '\0' */ | |
usize w_size = (size_ +1) - i; | |
/* avoid nested hell */ | |
if (*fmt != '}') | |
goto out0; | |
switch (tok_type) { | |
case 'i': | |
if (cstr_from_i64(buffer + i, &w_size, | |
va_arg(va, i32)) < 0) { | |
return -ENOMEM; | |
} | |
i += w_size; | |
continue; | |
case 'u': | |
if (cstr_from_u64(buffer + i, &w_size, | |
va_arg(va, u32)) < 0) { | |
return -ENOMEM; | |
} | |
i += w_size; | |
continue; | |
case 's': | |
if (cstr_copy1(buffer + i, &w_size, | |
va_arg(va, const char *)) < 0) { | |
return -ENOMEM; | |
} | |
i += w_size; | |
continue; | |
case 'c': | |
buffer[i++] = (char)va_arg(va, int); | |
continue; | |
} | |
out0: | |
if ((i + 3) < size_) { | |
buffer[i++] = '{'; | |
buffer[i++] = tok_type; | |
} else { | |
goto out1; | |
} | |
} | |
buffer[i++] = *fmt; | |
} | |
if (*fmt != '\0') | |
return -ENOMEM; | |
if (has_open_tok && ((i + 1) < size_)) | |
buffer[i++] = '{'; | |
if (find_close_tok && ((i + 1) < size_)) | |
buffer[i++] = tok_type; | |
out1: | |
buffer[i] = '\0'; | |
*size = i; | |
return 0; | |
} | |
static void | |
fmt_printfd(char buffer[], usize size, int fd, const char fmt[], ...) | |
{ | |
va_list va; | |
va_start(va, fmt); | |
(void)fmt_formatv(buffer, &size, fmt, va); | |
va_end(va); | |
cstr_print(buffer); | |
} | |
static int | |
fmt_format(char buffer[], usize *size, const char fmt[], ...) | |
{ | |
int ret; | |
va_list va; | |
va_start(va, fmt); | |
ret = fmt_formatv(buffer, size, fmt, va); | |
va_end(va); | |
return ret; | |
} | |
/* | |
* slots | |
*/ | |
static int | |
slots_init(Slots *self, u32 size) | |
{ | |
const int ret = mem_alloc1(&self->array, sizeof(u32) * size); | |
if (UNLIKELY(ret < 0)) | |
return ret; | |
self->count = 0; | |
self->size = size; | |
return 0; | |
} | |
static void | |
slots_deinit(Slots *self) | |
{ | |
mem_free(self->array); | |
} | |
static inline int | |
slots_push(Slots *self, u32 udata) | |
{ | |
const u32 count = self->count; | |
if (UNLIKELY(count == self->size)) | |
return -EAGAIN; | |
self->array[count] = udata; | |
self->count = count +1; | |
return 0; | |
} | |
static inline i64 | |
slots_pop(Slots *self) | |
{ | |
u32 count = self->count; | |
if (UNLIKELY(count == 0)) | |
return -EAGAIN; | |
count--; | |
self->count = count; | |
return (i64)self->array[count]; | |
} | |
/* | |
* queue | |
*/ | |
static int | |
queue_init(Queue *self, u32 nmemb, u32 size) | |
{ | |
if (mem_alloc1(&self->array, nmemb * size) < 0) | |
return 0; | |
self->count = 0; | |
self->tail = 0; | |
self->head = 0; | |
self->size = size; | |
self->nmemb = nmemb; | |
return 0; | |
} | |
static void | |
queue_deinit(Queue *self) | |
{ | |
mem_free(self->array); | |
} | |
static inline void * | |
queue_sqe(Queue *self) | |
{ | |
const u32 size = self->size; | |
const u32 count = self->count; | |
if (count == size) | |
return NULL; | |
u32 head = self->head; | |
void *const ret = &((u8 *)self->array)[self->nmemb * head]; | |
head++; | |
if (head >= size) | |
head = 0; | |
self->head = head; | |
self->count = count +1; | |
return ret; | |
} | |
static inline void * | |
queue_peek(Queue *self) | |
{ | |
if (self->count == 0) | |
return NULL; | |
return &((u8 *)self->array)[self->nmemb * self->tail]; | |
} | |
static inline void | |
queue_seen(Queue *self) | |
{ | |
u32 tail = self->tail +1; | |
if (tail >= (self->size)) | |
tail = 0; | |
self->tail = tail; | |
self->count--; | |
} | |
/* | |
* address | |
*/ | |
static int | |
address4_from(Address4 *self, const char addr[]) | |
{ | |
const usize size = sizeof(Address4); | |
u8 *p = (u8 *)self; | |
u8 dots = 0; | |
u16 sum = 0; | |
for (u8 i = 0; (*addr != '\0') && (i < size) && (dots <= 3); addr++) { | |
if (ascii_isdigit(*addr)) { | |
sum = (10 * sum) + (*addr - '0'); | |
if (sum > 255) | |
return -EINVAL; | |
p[i] = (u8)sum; | |
continue; | |
} | |
if (*addr == '.') { | |
sum = 0; | |
i++; | |
dots++; | |
continue; | |
} | |
break; | |
} | |
if ((dots != 3) || (*addr != '\0')) | |
return -EINVAL; | |
return 0; | |
} | |
static int | |
address6_from(Address6 *self, const char addr[]) | |
{ | |
return 0; | |
} | |
static int | |
address4_to(const Address4 *self, char buf[INET4_ADDRSTRLEN]) | |
{ | |
int ret; | |
const u8 *p = (const u8 *)self; | |
usize len = INET4_ADDRSTRLEN; | |
ret = fmt_format(buf, &len, "{u}.{u}.{u}.{u}", p[0], p[1], p[2], p[3]); | |
if (ret < 0) | |
return ret; | |
return 0; | |
} | |
static int | |
address6_to(const Address6 *self, char buf[INET6_ADDRSTRLEN]) | |
{ | |
return 0; | |
} | |
/* | |
* socket | |
*/ | |
static int | |
socket_listener(const char addr[], u16 port) | |
{ | |
int sfd; | |
isize ret; | |
SockAddressIn4 addri = { | |
.family = AF_INET4, | |
.port = mem_ntobe16(port), | |
}; | |
ret = address4_from(&addri.addr, addr); | |
if (ret < 0) { | |
PERROR(-ret, "address4_from"); | |
return -1; | |
} | |
ret = sys_socket(AF_INET4, SOCK_STREAM, IPPROTO_TCP); | |
if (os_error1(ret)) { | |
PERROR(-ret, "sys_socket"); | |
return -1; | |
} | |
sfd = (int)ret; | |
int y = 1; | |
ret = sys_setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &y, sizeof(y)); | |
if (os_error1(ret)) { | |
PERROR(-ret, "sys_setsockopt"); | |
goto err0; | |
} | |
ret = sys_bind(sfd, (SockAddress *)&addri, sizeof(addri)); | |
if (os_error1(ret)) { | |
PERROR(-ret, "sys_bind"); | |
goto err0; | |
} | |
ret = sys_listen(sfd, 30); | |
if (os_error1(ret)) { | |
PERROR(-ret, "sys_listen"); | |
goto err0; | |
} | |
/* Success */ | |
return sfd; | |
err0: | |
(void)sys_close(sfd); | |
return -1; | |
} | |
/* | |
* DateTime | |
*/ | |
static int | |
datetime_get(DateTime *self) | |
{ | |
const isize ret = sys_time(NULL); | |
if (os_error(ret)) | |
return (int)ret; | |
const Time time = (Time)ret; | |
/* TODO: parse unix time */ | |
self->year = 0; | |
self->month = 0; | |
self->day = 0; | |
self->hour = 0; | |
self->minute = 0; | |
self->second = 0; | |
return 0; | |
} | |
/* | |
* mysql | |
* maybe unsafe; leaking secret data | |
*/ | |
static void | |
mysql_init(MySQL *self, const char host[], const char port[], const char name[], | |
const char uname[], const char passwd[]) | |
{ | |
cstr_copy(self->host, sizeof(self->host), host); | |
cstr_copy(self->port, sizeof(self->port), port); | |
cstr_copy(self->name, sizeof(self->name), name); | |
cstr_copy(self->uname, sizeof(self->uname), uname); | |
cstr_copy(self->passwd, sizeof(self->passwd), passwd); | |
u32 len = 0; | |
self->args[len++] = "mysql"; | |
self->args[len++] = self->name; | |
self->args[len++] = "-u"; | |
self->args[len++] = self->uname; | |
self->args[len++] = "-p"; | |
self->args[len++] = self->passwd; | |
self->args_len = len; | |
} | |
static int | |
mysql_prep(MySQL *self, u32 count, ...) | |
{ | |
const u32 len = self->args_len -1; | |
if (UNLIKELY((count + len) >= MYSQL_ARGS_COUNT)) { | |
PERROR(EINVAL, "~count"); | |
return -EINVAL; | |
} | |
va_list va; | |
va_start(va, count); | |
usize i = 0; | |
for (; i < count; i++) | |
self->args[i + len] = va_arg(va, char *); | |
va_end(va); | |
self->args[i + len] = NULL; | |
return 0; | |
} | |
static int | |
mysql_exec(MySQL *self) | |
{ | |
isize ret = sys_fork(); | |
if (os_error1(ret)) { | |
PERROR(-ret, "sys_fork"); | |
return (int)ret; | |
} | |
if (ret == 0) { | |
ret = sys_execve("mysql", self->args, NULL); | |
if (os_error1(ret)) { | |
PERROR(-ret, "sys_execve"); | |
return (int)ret; | |
} | |
} else { | |
/* parent | |
* TODO: waitpid() | |
*/ | |
} | |
return 0; | |
} | |
/**************************************************************************** | |
* TLS * | |
***************************************************************************/ | |
/**************************************************************************** | |
* FSDNS * | |
***************************************************************************/ | |
static int | |
fsdns_resolve(const char addr[], const char port[]) | |
{ | |
return 0; | |
} | |
/*************************************************************************** | |
* FSCONF * | |
**************************************************************************/ | |
static int | |
fsconf_get(char dest[], usize size, const char src[], const char key[]) | |
{ | |
const char *start = cstr_find(src, key); | |
if (start == NULL) | |
return -EINVAL; | |
start += cstr_len(key); | |
if (*start != '(') | |
return -EINVAL; | |
/* skips '(' */ | |
start++; | |
const char *end = cstr_find(start, ")\n"); | |
if (end == NULL) | |
return -EINVAL; | |
const usize out_len = (usize)(end - start); | |
if (size <= out_len) | |
return -ENOMEM; | |
mem_copy(dest, start, out_len); | |
dest[out_len] = '\0'; | |
return 0; | |
} | |
/**************************************************************************** | |
* MailBox * | |
***************************************************************************/ | |
/**************************************************************************** | |
* POP3 * | |
***************************************************************************/ | |
/**************************************************************************** | |
* SMTP * | |
***************************************************************************/ | |
/**************************************************************************** | |
* main * | |
***************************************************************************/ | |
/* | |
* Global variables | |
*/ | |
Smtp *smtp_g; | |
Pop3 *pop3_g; | |
COLD static char * | |
init_config_file(usize *size) | |
{ | |
u8 try = 0; | |
u32 flags = O_RDONLY; | |
u32 mode = 0; | |
bool need_write = false; | |
char *fmap = NULL; | |
int fd; | |
Stat stat; | |
isize ret; | |
while (true) { | |
ret = sys_open(DEFAULT_CONFIG_FILE, flags, mode); | |
if (os_error(ret)) { | |
PERROR(-ret, "sys_open"); | |
cstr_print("Creating default config file...\n"); | |
need_write = true; | |
if ((-(int)ret == ENOENT) && (try < 3)) { | |
DPRINT("..\n"); | |
flags = O_RDWR | O_CREAT | O_TRUNC; | |
mode = 0644; | |
try++; | |
continue; | |
} | |
return NULL; | |
} | |
fd = (int)ret; | |
if (need_write) | |
break; | |
else | |
goto out0; | |
} | |
const usize cfg_len = sizeof(DEFAULT_CONFIG) -1; /* excluding '\0' */ | |
const char *cfg = DEFAULT_CONFIG; | |
usize wrt = 0; | |
while (wrt < cfg_len) { | |
ret = sys_write(fd, cfg + wrt, cfg_len - wrt); | |
if (os_error(ret)) { | |
PERROR(-ret, "sys_write"); | |
goto out1; | |
} | |
if (ret == 0) | |
break; | |
wrt += (usize)ret; | |
} | |
if (wrt != cfg_len) | |
goto out1; | |
/* do we need `lseek()` after `write()` ? */ | |
ret = sys_lseek(fd, 0, SEEK_SET); | |
if (os_error(ret)) { | |
PERROR(-ret, "sys_lseek"); | |
goto out1; | |
} | |
out0: | |
ret = sys_fstat(fd, &stat); | |
if (os_error(ret)) { | |
PERROR(-ret, "sys_fstat"); | |
goto out1; | |
} | |
ret = sys_mmap(NULL, (usize)stat.size, PROT_READ, MAP_PRIVATE, fd, 0); | |
if (os_error(ret)) { | |
PERROR(-ret, "sys_mmap"); | |
goto out1; | |
} | |
fmap = (char *)ret; | |
*size = (usize)stat.size; | |
out1: | |
(void)sys_close(fd); | |
return fmap; | |
} | |
static int | |
run_smtp(char *argv[]) | |
{ | |
Smtp smtp; | |
smtp_g = &smtp; | |
return 0; | |
} | |
static int | |
run_pop3(char *argv[]) | |
{ | |
Pop3 pop3; | |
pop3_g = &pop3; | |
return 0; | |
} | |
/* | |
* test functions | |
*/ | |
static void | |
test_num(void) | |
{ | |
char buf[100] = { 'a' }; | |
usize size = sizeof(buf); | |
if (cstr_find("jkdfjldfjdklf jd2i34uijlfjo", "2i34uijl") == NULL) | |
return; | |
if (cstr_from_i64(buf, &size, -11) < 0) | |
return; | |
if (!cstr_eql(buf, "-11")) | |
return; | |
if (size != 3) | |
return; | |
size = 20; /* expected length + '\0' */ | |
if (fmt_format(buf, &size, "ID: {i}: Num: {u}\n", -12, 1230) < 0) | |
return; | |
if (!cstr_eql(buf, "ID: -12: Num: 1230\n")) | |
return; | |
if (size != 19) | |
return; | |
if (!cstr_eql1("aaaa", "aabc", 2)) | |
return; | |
u64 uret; | |
if (cstr_to_u64("0000000000000001234567890", &uret) < 0) | |
return; | |
if (uret != 1234567890) | |
return; | |
i64 iret; | |
if (cstr_to_i64("-00001234567890", &iret) < 0) | |
return; | |
if (iret != -1234567890) | |
return; | |
cstr_print("passed\n"); | |
} | |
/* | |
* stupid_main | |
*/ | |
USED static noreturn void | |
stupid_main(int argc, char *argv[]) | |
{ | |
test_num(); | |
if (argc != 3) | |
goto out1; | |
int ret; | |
u64 port; | |
ret = cstr_to_u64(argv[2], &port); | |
if (ret < 0) { | |
PERROR(-ret, "cstr_to_u64"); | |
goto out1; | |
} | |
const int fd = socket_listener(argv[1], (u16)port); | |
if (fd < 0) | |
goto out1; | |
(void)sys_close(fd); | |
DPRINT("Ok!\n"); | |
out1: | |
(void)sys_exit(0); | |
UNREACHABLE; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment