We're doing a lot of allocation for duplicates of strings here - copies of each fragment in the string here, copies of each token within each fragment below - this isn't pretty.  Without too much effort, this outermost duplication could be avoided (while eliminating the complications for '\r' and empty lines, too): <code>
const char *file_pos = file_buffer;
while (file_pos)
{
    const char newlines[] = "\r\n";
    while (strchr(newlines, *file_pos))
    {
        file_pos++;
    }
    size_t linelen = strcspn(file_pos, newlines);
    if (!linelen)
    {
        assert(!file_pos[0]);
        break;
    }
    char *const line = strndup(file_pos, linelen);
    file_pos += linelen;
    /* process line as below */
    free(line);
}
</code>