diff options
| -rw-r--r-- | .gitignore | 4 | ||||
| -rw-r--r-- | LICENSE | 13 | ||||
| -rw-r--r-- | Makefile | 16 | ||||
| -rw-r--r-- | README | 9 | ||||
| -rw-r--r-- | handle.c | 160 | ||||
| -rw-r--r-- | ircd.h | 74 | ||||
| -rw-r--r-- | loop.c | 134 | ||||
| -rw-r--r-- | main.c | 52 | ||||
| -rw-r--r-- | message.c | 81 | ||||
| -rw-r--r-- | peer.c | 73 | ||||
| -rw-r--r-- | user.c | 50 | ||||
| -rw-r--r-- | writef.c | 40 |
12 files changed, 706 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..30d2722 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.core +*.o +*.out +ircd diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..7802b18 --- /dev/null +++ b/LICENSE @@ -0,0 +1,13 @@ +Copyright (c) 2026 Naidai Perumenei <nakidai at disroot at org> + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5953be1 --- /dev/null +++ b/Makefile @@ -0,0 +1,16 @@ +NAME ?= ircd + +CFLAGS += -Wall -Wextra + +OBJS += handle.o +OBJS += loop.o +OBJS += main.o +OBJS += message.o +OBJS += peer.o +OBJS += user.o +OBJS += writef.o + +${NAME}: ${OBJS} + ${CC} -o ${NAME} ${CFLAGS} ${LDFLAGS} ${OBJS} +clean: + rm -f ${NAME} ${OBJS} diff --git a/README b/README new file mode 100644 index 0000000..473e983 --- /dev/null +++ b/README @@ -0,0 +1,9 @@ +LibreIRCd +========= +Simple IRC server in C + +usage: ircd hostname bindaddr port + +--------------- +This project is incomplete: channels, modes, a lot of validation is not present +yet. diff --git a/handle.c b/handle.c new file mode 100644 index 0000000..79a3b9a --- /dev/null +++ b/handle.c @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2026 Nakidai Perumenei <nakidai at disroot dot org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "ircd.h" + +#include <stddef.h> +#include <stdio.h> +#include <string.h> + + +#define ensure(cond, iffalse, doexit) if (!(cond)) { iffalse; if (doexit >= 0) return doexit; } + +static int +nick(struct Message *msg, struct Peer *peer) +{ + ensure(msg->params[0] && msg->params[0], writef( + peer->fd, + ":%s 431 %s :No nickname given", + hostname, + getnick(peer) + ), 0); + + user_reg(peer, msg->params[0], NULL, NULL); + + return 0; +} + +static int +privmsg(struct Message *msg, struct Peer *peer) +{ + size_t i; + + ensure(peer->type, writef( + peer->fd, + ":%s 451 %s :You have not registered", + hostname, + getnick(peer) + ), 0); + ensure(msg->params[0] && *msg->params[0], writef( + peer->fd, + ":%s 411 %s :No recipient given (PRIVMSG)", + hostname, + getnick(peer) + ), 0); + ensure(msg->params[1] && *msg->params[1], writef( + peer->fd, + ":%s 412 %s :No text to send", + hostname, + getnick(peer) + ), 0); + + /* TODO: implement channels */ + ensure(!strchr("!+#&", *msg->params[0]), writef( + peer->fd, + ":%s 421 %s :Channels are not supported yet", + hostname, + getnick(peer) + ), 0); + + /* TODO: implement server-to-server communication */ + for (i = 0; i < PEERS_MAX; ++i) + if (peers[i].type == CLIENT && !strcmp(peers[i].nick, msg->params[0])) + writef( + peers[i].fd, + ":%s!%s@%s PRIVMSG %s :%s", + peer->nick, + peer->user, + peer->host, + peer->nick, + msg->params[1] + ); + return 0; +} + +static int +quit(struct Message *msg, struct Peer *peer) +{ + return 1; +} + +static int +user(struct Message *msg, struct Peer *peer) +{ + size_t i; + + ensure(!peer->type, writef( + peer->fd, + ":%s 462 %s :Unauthorized command (already registered)", + hostname, + getnick(peer) + ), 0); + for (i = 0; i < 4; ++i) + ensure(msg->params[i] && *msg->params[i], writef( + peer->fd, + ":%s 461 %s %s :Not enough parameters", + hostname, + getnick(peer), + msg->command + ), 0); + + user_reg(peer, NULL, msg->params[0], msg->params[3]); + + return 0; +} + +static int +default_handler(struct Message *msg, struct Peer *peer) +{ + ensure(peer->type, 0, 0); + + writef(peer->fd, ":%s 421 %s %s :Unknown command", hostname, getnick(peer), msg->command); + return 0; +} + +static struct Handler { + const char *name; + Handler *handler; +} handlers[] = +{ + { "nick", nick }, + { "privmsg", privmsg }, + { "quit", quit }, + { "user", user }, +}; + +Handler * +find(const char *command) +{ + struct Handler *l, *m, *r; + int res; + + l = handlers; + r = handlers + lengthof(handlers) - 1; + while (l <= r) + { + m = l + (r - l) / 2; + res = strcmp(command, m->name); + if (res > 0) + l = ++m; + else if (res < 0) + r = --m; + else + return m->handler; + } + + return default_handler; +} diff --git a/ircd.h b/ircd.h new file mode 100644 index 0000000..5e787da --- /dev/null +++ b/ircd.h @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2026 Nakidai Perumenei <nakidai at disroot dot org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef __IRCD_H__ +#define __IRCD_H__ + +#include <stddef.h> + + +#define PARAM_MAX 15 +#define PEERS_MAX 512 +#define MESSAGE_MAX 512 + +#define BIT(x) x##_BIT, x = 1 << x##_BIT, x##_BIT_ = x##_BIT +#define lengthof(X) (sizeof(X) / sizeof(*(X))) + +struct Message +{ + char *nick, *user, *host, *command, *params[PARAM_MAX]; +}; + +struct Peer +{ + int fd; + enum ClientType { UNREGED, CLIENT, SERVER, SERVICE } type; + char nick[16], user[16], real[32], host[64]; + char **channels; + size_t channels_count; + char modes[52]; + char buf[MESSAGE_MAX]; + size_t recvd; +}; + +struct Channel +{ + enum { GLOBAL, LOCAL, MODELESS, SAFE } type; + char name[64]; + struct { + char mode; + char param[MESSAGE_MAX - 1]; + } *modes; + size_t modes_count; +}; + +typedef int Handler(struct Message *msg, struct Peer *peer); + +extern struct Peer peers[PEERS_MAX]; +extern const char *hostname; +extern const char *host; +extern int port; + +const char *getnick(const struct Peer *peer); +void user_reg(struct Peer *peer, const char *nick, const char *user, const char *real); + +int parse_message(char *buf, struct Message *msg); +int handle(struct Peer *peer); +Handler *find(const char *command); +int writef(int fd, const char *fmt, ...); +void ircd(void); + +#endif /* __IRCD_H__ */ diff --git a/loop.c b/loop.c new file mode 100644 index 0000000..ba14a4d --- /dev/null +++ b/loop.c @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2026 Nakidai Perumenei <nakidai at disroot dot org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "ircd.h" + +#include <string.h> + +#include <err.h> +#include <poll.h> +#include <signal.h> +#include <unistd.h> + +#include <arpa/inet.h> +#include <netinet/in.h> +#include <sys/socket.h> +#include <sys/types.h> + + +struct Peer peers[PEERS_MAX]; +static struct pollfd pfd[PEERS_MAX + 1]; +static size_t remove_schedule[PEERS_MAX]; + +const char *host; +int port; + +void +ircd(void) +{ + size_t i, schedulei, connected, pid; + struct sockaddr_in addr, clientaddr; + int res, sfd, client; + nfds_t passed; + ssize_t recvd; + + addr.sin_family = PF_INET; + addr.sin_port = htons(port); + + res = inet_pton(AF_INET, host, &addr.sin_addr); + if (res == -1) + err(1, "inet_pton()"); + + sfd = socket(AF_INET, SOCK_STREAM, 0); + if (sfd == -1) + err(1, "socket()"); + + res = setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int)); + if (res == -1) + err(1, "setsockopt(SO_REUSEADDR)"); + + res = bind(sfd, (void *)&addr, sizeof(addr)); + if (res == -1) + err(1, "bind()"); + + res = listen(sfd, 128); + if (res == -1) + err(1, "listen()"); + + signal(SIGPIPE, SIG_IGN); + for (connected = 0;;schedulei = 0) + { + pfd[connected] = (struct pollfd){ .fd = sfd, .events = POLLIN }; + for (i = 0; i < connected; ++i) + pfd[i] = (struct pollfd) + { + .fd = peers[i].fd, + .events = POLLIN, + }; + res = poll(pfd, (passed = connected) + 1, -1); + if (res == -1) + err(1, "poll()"); + + if (pfd[connected].revents & POLLIN) + { + client = accept(sfd, (void *)&clientaddr, &(int){sizeof(clientaddr)}); + if (client == -1) + { + warn("accept(sfd)"); + } + else if (connected == PEERS_MAX) + { + /* + * TODO: maybe send 005 or smth? + */ + close(client); + } + else + { + peers[connected++] = (struct Peer){ .fd = client }; + strlcpy( + peers[connected - 1].host, + inet_ntoa(clientaddr.sin_addr), + sizeof(peers->host) + ); + } + } + + for (i = 0; i < passed; ++i) + { + if (!(pfd[i].revents & POLLIN)) + continue; + + recvd = read( + peers[i].fd, + peers[i].buf + peers[i].recvd, + sizeof(peers[i].buf) - peers[i].recvd + ); + peers[i].recvd += recvd; + if (recvd == -1 || !recvd || handle(&peers[i])) + remove_schedule[schedulei++] = i; + } + for (i = 0; i < schedulei; ++i) + { + pid = remove_schedule[i]; + /* TODO: announce_quit(&peers[pid]); */ + writef(peers[pid].fd, "ERROR :Closing Link: %s", peers[pid].host); + close(peers[pid].fd); + peers[pid] = peers[--connected]; + } + schedulei = 0; + } +} diff --git a/main.c b/main.c new file mode 100644 index 0000000..8be182a --- /dev/null +++ b/main.c @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2026 Nakidai Perumenei <nakidai at disroot dot org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "ircd.h" + +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> + +#include <err.h> +#include <unistd.h> + + +const char *hostname; + +int +main(int argc, char **argv) +{ + size_t i; + char *p; + + for (i = 1; i < 4; ++i) + if (!argv[i] || !*argv[i]) + errx(1, "usage: %s hostname bindaddr port", argv[0]); + hostname = argv[1]; + host = argv[2]; + port = strtod(argv[3], &p); + if (*p || port <= 0 || port > 65535) + errx(1, "invalid port"); + +#ifdef __OpenBSD__ + if (unveil(NULL, NULL)) + err(1, "unveil()"); + if (pledge("stdio rpath inet", "")) + err(1, "pledge()"); +#endif /* __OpenBSD__ */ + + ircd(); +} diff --git a/message.c b/message.c new file mode 100644 index 0000000..f394ff7 --- /dev/null +++ b/message.c @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2026 Nakidai Perumenei <nakidai at disroot dot org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "ircd.h" + +#include <stddef.h> +#include <string.h> + + +int +parse_message(char *buf, struct Message *msg) +{ + char *t, *next; + size_t i; + + memset(msg, 0, sizeof(*msg)); + + if (*buf == ':') + { + t = ++buf; + next = strchr(buf, ' '); + if (!next) + return 1; + *next++ = 0; + buf = next; + + next = strchr(t, '@'); + if (next) + { + *next++ = 0; + if (!*t) + return 1; + + msg->nick = t; + t = next; + + next = strchr(msg->nick, '!'); + if (next) + { + *next++ = 0; + if (!*msg->nick || !*next) + return 1; + msg->user = next; + } + } + if (!*t) + return 1; + msg->host = t; + } + + msg->command = buf; + t = strstr(buf, " :"); + if (t) + *((t += 2) - 2) = 0; + for (i = 0; i < PARAM_MAX - !!t; ++i, buf = next) + { + next = strchr(buf, ' '); + if (!next) + break; + *next++ = 0; + msg->params[i] = next; + } + if (strchr(buf, ' ')) + return 1; + if (t) + msg->params[i] = t; + return 0; +} diff --git a/peer.c b/peer.c new file mode 100644 index 0000000..21dba70 --- /dev/null +++ b/peer.c @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2026 Nakidai Perumenei <nakidai at disroot dot org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "ircd.h" + +#include <ctype.h> +#include <stdio.h> +#include <stddef.h> +#include <string.h> + +#include <err.h> + + +static void +move(struct Peer *peer, size_t n) +{ + memmove(peer->buf, peer->buf + n, sizeof(peer->buf) - n); + memset(peer->buf + sizeof(peer->buf) - n, 0, n); + peer->recvd -= n; +} + +int +handle(struct Peer *peer) +{ + struct Message msg; + char *p, *c; + + while ((p = memmem(peer->buf, sizeof(peer->buf), "\r\n", 2))) + { + if (memchr(peer->buf, '\0', p - peer->buf)) + { + warnx("got 0 from %d", peer->fd); + goto next; + } + + *p = 0; + if (parse_message(peer->buf, &msg)) + { + warnx("malformed input from %d", peer->fd); + goto next; + } + + for (c = msg.command; *c; ++c) + *c = tolower(*c); + if (find(msg.command)(&msg, peer)) + return 1; +next: + move(peer, p - peer->buf + 2); + } + if (peer->recvd == sizeof(peer->buf)) + { + /* + * TODO: maybe somehow be more careful with peer data? + * or drop them? + */ + memset(peer->buf, 0, sizeof(peer->buf)); + peer->recvd = 0; + } + return 0; +} diff --git a/user.c b/user.c new file mode 100644 index 0000000..4411144 --- /dev/null +++ b/user.c @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2026 Nakidai Perumenei <nakidai at disroot dot org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "ircd.h" + +#include <string.h> + + +const char * +getnick(const struct Peer *peer) +{ + return *peer->nick ? peer->nick : "*"; +} + +void +user_reg(struct Peer *peer, const char *nick, const char *user, const char *real) +{ + if (nick) + strlcpy(peer->nick, nick, sizeof(peer->nick)); + if (user) + strlcpy(peer->user, user, sizeof(peer->user)); + if (real) + strlcpy(peer->real, real, sizeof(peer->real)); + if (*peer->nick && *peer->user && *peer->real) + { + peer->type = CLIENT; + writef( + peer->fd, + ":%s 001 %s :Welcome to the Internet Relay Network %s!%s@%s", + hostname, + getnick(peer), + getnick(peer), + peer->user, + peer->host + ); + } +} diff --git a/writef.c b/writef.c new file mode 100644 index 0000000..7d57e3b --- /dev/null +++ b/writef.c @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2026 Nakidai Perumenei <nakidai at disroot dot org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "ircd.h" + +#include <stdarg.h> +#include <stdio.h> +#include <string.h> + +#include <unistd.h> + + +int +writef(int fd, const char *fmt, ...) +{ + static char buf[MESSAGE_MAX + 1]; + va_list args; + int written; + + va_start(args, fmt); + written = vsnprintf(buf, sizeof(buf) - 2, fmt, args); + va_end(args); + written = written < sizeof(buf) - 2 ? written : sizeof(buf) - 2; + memcpy(buf + written, "\r\n", 3); + + return write(fd, buf, written + 3); +} |