8 days ago by knorker

A little sloppy with the error handling, but pretty neat.

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.

8 days ago by mcpcpc

i appreciate the honest feedback! definitely a work in progress and i have learned a lot since i started ~1mo ago on this. will take your suggestions and add them to my ever growing “todo” list. ;)

8 days ago by stingraycharles

> malloc() return isn't checked

small digression, but I thought that at least on Linux, malloc() never returns an error because the actual allocation happens lazily, when the memory is first used?

8 days ago by fulafel

Cases where memory allocations fail on linux include: hitting memory resource limits (ulimit), overcommit behaviour is set to stricter than the default via sysctl, kernel heuristics with the default overcommit settings end up failing allocation, container / cgroups limits, 32-bit virtual address space is exhausted - i'm sure there are more.

The default overcommit-within-reason algorithm is designed to deny allocations that are obviously unrealistic I think.

Using semi conservative ulimit settings is pretty common in interactive use to catch runaway / swapped to death situations.

8 days ago by nyc_pizzadev

Take a look at vm.max_map_count. A lot of systems have that set artificially low and that will force a memory allocator to fail and return NULL. I can confirm this is the case for jemalloc.

Also, on Linux, only 48 of 64 bits are available for VM addressing. That leaves you with about 250TB of address space. Seems like a lot until you start using VM for disk mmaps. I have actually seen this limit hit.

8 days ago by moonchild

> on Linux, only 48 of 64 bits are available for VM

That is on amd64, not linux. And arm does the same thing. (It's also 256tb, not 250tb.)

Newer intel cpus use 5-level paging[1], which gives you 128pb VM.

1. https://en.wikipedia.org/wiki/Intel_5-level_paging

8 days ago by simias

It's sloppy coding and not portable. If you really don't want to check for malloc() return value because you consider that it shouldn't fail (or that you won't be able to recover from it anyway) just implement an xmalloc() that just aborts on failure and do that. At least it'll crash "cleanly" this way.

7 days ago by mcpcpc

will try to address the malloc concerns asap

8 days ago by formerly_proven

On 64 bits malloc basically always succeeds, because you are very likely not running out of virtual memory. You can totally go ahead and malloc terabytes of RAM.

On 32 bits malloc is going to fail once your virtual memory space is full (~3 GB).

Error checking is somewhat redundant for most applications, since you're going to abort anyway, and if there are no pages available on access - well you're getting SIGSEGV anyway, just like you would accessing a null pointer (except on embedded devices). But beware of the exceptions. In C especially it's common to use null pointers as a flag... one of the reasons why new/delete in C++ is preferable; it throws and thus aborts when it fails, which you don't care to handle, so that's about perfect for a C++ exception.

8 days ago by undefined


8 days ago by tmsbrg

IRC in 307 rules of C code without dependencies, pretty cool. Of course for that to work they had to sacrifice secure TLS support. Based on the "do one thing well" I was thinking you could set up a TLS tunnel from localhost:6667 to <irc-server>:6697

Not really perfect but my WIP attempt using `socat`:

`socat -v tcp-listen:6667,reuseaddr,fork,bind= ssl:<irc-server>:6697`

then connect to it:

`./kirc -s -c 'channel' -n 'name' -r 'realname'`

not sure what TLS version socat uses by default, might be something horrible :)

8 days ago by mcpcpc

oohh interesting. will throw this in as an “example”. thanks!

8 days ago by aidenn0

stunnel is a more dedicated SSL prox as well.

7 days ago by zoggi

> stunnel is a more dedicated SSL prox as well.

New to stunnel, could you show an example command line?

EDIT: done with a .conf file but somehow failed with a command line...

8 days ago by visualphoenix

FWIW I’ve had great success using ghostunnel[0] instead of stunnel in prod. Big fan.

[0] https://github.com/ghostunnel/ghostunnel

8 days ago by 1vuio0pswjnm7

What was the need for an stunnel "replacement"? Looks like it has additional access controls and was written for use at Square, Inc.

stunnel supports TLS1.3; from the client side IME it works really well

ghostunnel appears to have a large number of dependencies on Go libraries written by a variety of third party authors

stunnel is a long-running project dating from 1998, managed by the same single author over the entire period

8 days ago by vaccinator

Any chance you know how to do encrypted dcc sends too?

8 days ago by anthk

Thanks, this will work for SIC too.

8 days ago by userbinator

The IRC protocol is both text-based and simple enough that you can use something like netcat as a client (and I have, many times.) The line-based format fits IM perfectly, and the overhead of the protocol is a tiny fraction of the bloated proprietary ones that have filled this use-case today (except MSNP, which in its earlier versions was also delightfully simple and easy to write a client for, but definitely beyond the threshold of being usable "raw".)

8 days ago by nyuszika7h

The problem with that is that IRC networks generally send you a ping every 5 minutes or so, and if you don't send a PONG back in time you are disconnected.

8 days ago by Macha

It varies on network but the timing on that was pretty generous. Seen some networks where it gave you entire minutes to respond

8 days ago by sys_64738

I've used telnet as the client wait in the distant pass. Those were the days.

8 days ago by Fnoord

You could use netcat (or something akin to it). Back in the days I tunneled that via stunnel to get TLS [1] support. One could do the same with POP3 and IMAP.

One problem with this workflow is that in some clients (e.g. IRC client supporting TLS) the user had no way to identify/verify the certificate. If a client just automatically accept self-signed certificates, its just snake oil.

[1] Everyone still called it SSL back then. Oh, wait...

8 days ago by a1369209993

> If a client just automatically accept self-signed certificates, its just snake oil.

It forces attackers to use a active attack rather than a passive one. Which is the only security most IRC can have anyway, since the attacker could just join the channel and listen in that way, since most IRC networks are public.

8 days ago by sys_64738

I never did SSL from telnet for sure, in fact I didn't know IRC supported encryption back then. There was also the ident response that was needed - can't recall the particulars as it's been 20+ years.

8 days ago by hombre_fatal

telnet for MUDs too. Such a simple interface for such complex worlds is something that still fascinates me.

8 days ago by anthk

And Nethack/Slashem servers :D.

So you can play RPGs remotely from any crap built from the 80's with serial support and a 80x24 display (WIFI232), on the display, a Spectrum +3 would suffice.

8 days ago by smabie

Like books

8 days ago by dependenttypes

IRC has the issue that it lacks end to end encryption (and sadly OTR is outdated garbage)

If you use netcat you also do not use tls, try openssl s_client -connect server:port instead.

8 days ago by snazz

I generally support end-to-end encryption for everything, but I'm not sure that it makes sense in the context of IRC. IRC networks are usually public, so anyone could join your channel and listen in, even with end-to-end encryption. It seems like E2E would make for a lot of complexity and overhead without tangibly increasing the privacy of the users.

8 days ago by Fnoord

Before E2EE was used in IM clients, IRC already had IRC over TLS, and also OTR (which was also used in Gaim/Pidgin).

On IRC, IRC over TLS doesn't have the same threat model as E2EE. With IRC over TLS, the server(s) can read the data plaintext. With proper E2EE (not the marketing version) that's not the case; only clients can read the data. I'm talking about actual data/content here; not metadata.

8 days ago by oarsinsync

> IRC networks are usually public, so anyone could join your channel and listen in, even with end-to-end encryption.

Yep, and all they'd see is encrypted garbage, unless they have encryption keys, if the messages are end-to-end encrypted. That's the whole point.

There are ways to do this on IRC (e.g. libfish), but no idea how that crypto actually stacks up by todays standards.

8 days ago by dependenttypes

There are private IRC channels (password protected or invite-only) as well as private messages.

8 days ago by CyberRabbi

Cool project. I would relax the requirements from C99 to C89. You get more portability to cool retro systems that way and C99 doesn’t really add that much. Also C++ compilers are generally more able to compile C89 in C++ mode than C99, e.g. msvc

8 days ago by simias

I thought MSVC added support for C99 at long last a few years ago? IIRC because it was effectively needed for a newer C++ standard, but still.

Also it's 2020, I wasn't even coding when C99 came out and I'm now a "senior" developer, whatever that means. You'll have to pry C99 from my cold, dead hands. Whatever compilers don't support it by now, I don't want to support them.

8 days ago by smabie

What do you think C99 adds that's so important? The only thing that comes to mind is declaring dynamic arrays in which the length is known at the call of the function on the stack. But using malloc isn't so hard regardless.

I haven't programmed in C in over 10 years though, so I could be missing something.

8 days ago by berti

Designated initialisers for structs and unions are a really nice readability improvement. You can use comments if you have to of course.

7 days ago by simias

stdint.h, inline, real booleans, designated initializers, snprintf etc...

8 days ago by Narishma

I don't think so. I think they added (or will add?) support for C11 and C17, but not C99.

8 days ago by mcpcpc

definitely a good idea. will add that to the “todo” list

8 days ago by snvzz

To the contrary, I wouldn't.

Using C99 + POSIX is the defining fact about your project.

I would let somebody else write a C89 irc client, instead.

8 days ago by flohofwoe

Agreed, also "retro system compiler" doesn't mean it only supports old C standards, e.g. SDCC is C99 and C11 compatible. IMHO strict C89 is a much less enjoyable language to read and write than C99, for instance designated initialization and compound literals are massive improvements.

Also MSVC's C99 support is pretty good since ca. VS2015.

8 days ago by petre

Why not write it in betterC?


8 days ago by zserge

as well as a file-based tiny IRC client in C: http://tools.suckless.org/ii/

8 days ago by mcpcpc

the suckless IRC clients are awesome! in fact, `sic` was my “go to” before writing `kirc`. I’m definitely not trying to compete with those, especially their file-based approach (which is great for users that work across channels) but rather offer a lightweight and “clean-looking” solution for the casual user.

8 days ago by messe

This has probably a repeat of what would have been said a decade ago about c89/c99, but why use c99 over c11?

8 days ago by chriszhang

Not trying to be snarky. Honest question: Why use C11 over C99? Or even why use C99 over C89? What significant advantages do the new standards provide that cannot be done in plain old C89?

8 days ago by beefhash

> Why use C11 over C99?

C11 gives you noreturn and alignas. Alignas can be pretty useful for low-level development in particular. Just hope you don't need variable-length arrays because those got changed to optional.

> Or even why use C99 over C89?

Several very big things: Native bool, stdint.h (fixed-width int types with known sizes ahead of time), long long, snprintf, not having to declare all variables at the top of the block (and now you can do for (size_t i = 0; i < sizeof(strbuf); ++i) because of it).

8 days ago by masklinn

Designated initialisers.

8 days ago by luke0016

For C99, don't forget // C++ style comments!

8 days ago by marmaduke

snprintf is pretty great for any sort of logging or error messages.

8 days ago by williamdclt

You don't need any "significant" advantage. Even a very small advantage ("an anonymous struct would be handy here") is enough, why would you _not_ use it when it's free? For the fun of the constraint? I'm not a C expert but I don't think there's any downside to using the C11 standard compared to C99

8 days ago by jlokier

One downside is reduced portability. If you're writing in C that's often a motivation.

The number of platforms with a C11 compiler is lower than the number of platforms with a C99 compiler.

8 days ago by superkuh

When you start using new features you break backwards compilability. For C11 that means distros as new as Ubuntu 10.04 (which I still use as my main desktop) and the like are going to have problems compiling (GCC it ships with only supports parts, as in C1X). This will also apply to older embedded systems where a tiny client would be useful.

In the past a compiler and ecosystem would last a decade before it couldn't compile something. These days changes are coming out, and being used, every 3 years. It's future shock and the major cause of container usage on the desktop and in academia. Sticking with a well established older standard means everyone can avoid the massive increase in complexity and problems that containers bring.

8 days ago by phone8675309

Older standards typically have a larger pool of people who can contribute because the standard has been around longer.

A programmer might have more experience with an older standard due to the length of time has been out or because the toolchain they use elsewhere (personal projects, embedded comes to mind, or work) hasn't updated to the new standard.

Coming up to speed with the new standard is not free. The tooling may be free for the most common targets (embedded usually lags), but taking the time to learn isn't free.

8 days ago by archi42

I'm currently porting some "C99"-ish code, and I had a few instances for which I would have loved just use C11's `_Generic` to replace a macro-hell with statement expressions and accompanying `typeof()`s all over the place. Fun fact: `typeof` is a GNU extension, and the target compiler doesn't have that.

8 days ago by nrclark

In addition to other's comments, C11 also gives you:

  - static_assert, for ensuring things at compile-time without ugly macros.
  - atomics, for use in multi-threaded systems.

8 days ago by mcpcpc

good question! no real reason, other than C99 being the standard that i started development in. With that said, I see no reason not to switch ;)

8 days ago by mmozeiko

This source code seems tiny - just ~300 lines. What features of c11 do you think would be useful to use there?

8 days ago by lokedhs

I actually feel bad about pointing out issues with this code since the project is very neat. But, I'm still going to do it.

There is a problem where the code uses explicit escape sequences for colour instead of using terminfo. This is a pet peeve of mine, because it prevents things like controlling whether or not to use colour by setting TERM to the appropriate values. Or to completely disable highlighting by setting TERM to "dumb". Or even use a completely different terminal type, like if you have an old vt52 hooked up to your computer.

Terminfo is a really nice library that abstracts away all the terminal codes. It's really what should be used here.

7 days ago by mcpcpc

interesting argument. will look into it

8 days ago by projektfu

Looks cool. I wonder whether users can overflow your buffers inputting commands in sscanf. Also, why malloc/free cmd_str in raw? You're automatically or statically allocating all the other buffers.

8 days ago by mcpcpc

will fix that. thank you!

Daily Digest

Get a daily email with the the top stories from Hacker News. No spam, unsubscribe at any time.