E.g. log_append() doesn't check fopen return value, malloc() return isn't checked, and a write() can return a partial write, thus needs a loop. fcntl().
Also write() should check for EINTR&EAGAIN.
And also there's no handling for nonblocking write.
If the user types too much while the network glitches it seems that this could cause the client to exit().
Probably the correct way is to not read from stdin unless poll() returns that writing to the socket is safe, and vice versa.
connect() errors should print where they failed to connect.
And: $ ./kirc Nick not specified: Success
And in my first test I got: Write to socket: Resource temporarily unavailable
And I see a lot of NULL pointers being printed when I connect to e.g. freenode.
And so on, and so on…
For these things I recommend re-reading the manpage for every libc and syscall you call, and check how they can fail, and consider how you can handle that failure.
307 lines of pure C is a pretty neat minimal actually usable client, so I'm not saying it's not well done. But that thing about "… and do it well" (from the readme) means doing all of the above.
The problem, of course, is that fixing these problems well is "the other 90% of the work", especially when coding in C.
And this is the main reason I avoid C when I can. You can't just call "write()". You have to write a 5-10 line wrapper function, and then all callers need a few lines of error handling. And so it goes for everything, until your program is no longer the nice 300 line neat thing you can almost write from memory.
So it's a good start. But it's pretty fragile in its current form.