Kirc – A tiny IRC client written in POSIX C99
github.com
github.com
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.
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?
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.
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.
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.
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.
And that's part of the "and do it well". A thousand years from now, if the C99&POSIX spec survives, will support correctly written code.
Dereferencing a null pointer can also be a security issue (mostly for kernels, though) so one should assume a malicious general environment.
Not really perfect but my WIP attempt using `socat`:
`socat -v tcp-listen:6667,reuseaddr,fork,bind=127.0.0.1 ssl:<irc-server>:6697`
then connect to it:
`./kirc -s 127.0.0.1 -c 'channel' -n 'name' -r 'realname'`
not sure what TLS version socat uses by default, might be something horrible :)
New to stunnel, could you show an example command line?
EDIT: done with a .conf file but somehow failed with a command line...
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
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...
Some servers required ident(d) response which required a server running on privileged port 113.
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.
The MITM or eavesdrop can happen on a bridge. If the client doesn't check the certificate and accepts any, its about as good as plaintext. It could be worse, even, due to the false sense of security.
> 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.
IRC network private or public is irrelevant.
There were, for sure, private channels back in the days (90s). Back then you could set a channel secret (hidden) and set a password on it, effectively making it a private channel (would not show up in /whois or /list). Bots could kick people who are unknown based on filters. For example, without an auth to an Eggdrop, you could get insta kickbanned even _with_ the correct password.
Then there's PMs which are one on one (except for server(s)).
If one of the IRC servers is compromised though (or tapped, or whatever), that makes sniffing a channel or PMs child play.
There's also the problem of data integrity. If you are asking for (or giving) help in #linux and someone can change the data on the fly, [...]
FWIW, UnrealIRCd, even back then, innovated (or invented) a lot of new features on top of IRC. Some of these added security, though I don't know examples out of my head.
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.
If you use netcat you also do not use tls, try openssl s_client -connect server:port instead.
if it's public: who cares, and if it isn't: why are people trusting random IRC server admins
especially when there have previously been leaks from places like EFNet where admins have been caught running tcpdump or ircsniff.pl
e2ee means that you do not have to trust anyone
> if it's public: who cares
IRC also lacks end to end authentication, the server owner can pretend to be you.
Fortunately, there's OTR, but client support is limited.
I wish the new ircstandarization efforts did work something out about e2e, at least for private messages.
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.
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.
Yep, and they would have the encryption keys, for most channels, if the channels are to remain public, no?
Using C99 + POSIX is the defining fact about your project.
I would let somebody else write a C89 irc client, instead.
Also MSVC's C99 support is pretty good since ca. VS2015.
Naturally like everyone else in the C world, with exception of gcc/clang, all C99 features that became optional in C11 aren't planned to be supported.
But yeah, those C99 features weren't consistent at all with Herb Sutter's 2012 blog post about MSVC only supporting C features that are needed for the C++ compiler.
C and C++ are more strictly separated in the Microsoft compilers compared to gcc and clang (which both support more modern C features in C++ mode via non-standard extensions), I think that's what's confusing many people. It's not a problem in mixed-language projects though, just put all the C code into .c files and all C++ code into .cpp and you're set, compiling C code with a C++ compiler isn't such a great idea anyway, since it limits you to a ca. 1995 version of C.
Could just as well ask why not write it in Nim, Zig, Rust, Swift, Kotlin etc... This means a different audience, different target platforms, different trade-offs.
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.
I haven't programmed in C in over 10 years though, so I could be missing something.
The number of platforms with a C11 compiler is lower than the number of platforms with a C99 compiler.
There's also the fact that there are far more compilers for C89, and it is easier to write one than for the newer standards. This becomes important if you are interested in avoiding Ken Thompson attacks.
Personally, I still stick to C89 and the only newer feature that I've found to be useful is mixed declarations and statements, but it's no big loss as it both avoids the "variable proliferation" that some codebases seem to be afflicted with, and blocks can be used to start a new inner scope if you really need a new set of declarations anyway.
I'm also not sure why they would be relevant for a general project, since the source language being easy to write a alternate compiler for only matters for the compiler itself: once you have non-infected compiler, you can bootstrap gcc or whatever and compile everything else at whatever C standard you like.
It's the first thing that came to mind when I thought of the concept. Perhaps this discussion may yield some additional insight: https://news.ycombinator.com/item?id=24385389
My only pain point is indeed stdint.h. Though it's often available everywhere even if not standard per se.
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.
> or because the toolchain they use elsewhere (personal projects, embedded comes to mind, or work) hasn't updated to the new standard.
gcc and clang both support it.
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.
Lets just say it's a matter of taste. I keep my attack surfaces to a minimum, backport what I can "patch and statically compiled deps for userspace"-wise. On the otherhand, I browse the web with javascript disabled so my old box probably has less "horrible security implications" than a completely up to date distro with the user blindly executing all code they're sent. Security is behavior more than software.
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).
- static_assert, for ensuring things at compile-time without ugly macros.
- atomics, for use in multi-threaded systems.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.