/* * Copyright (c) Alex Holden 2000, 2001. * * This code is public domain; you can do whatever you want with it, though I * would prefer you to contribute any bug fixes back to me if possible. * This software comes with NO WARRANTY WHATSOEVER, not even the implied * warranties of merchantability and fitness for a particular purpose. * * base64.c: Read and write using base64. * * These routines are intended to be compatible with rfc1521.txt, though I * haven't tested them against other implementations as I wrote them * specifically for the Base64 mode in TEA Total. They depend on lineget() * (see lineget.c) to get line oriented input without using the stdio library, * but you could probably modify them to use getline() instead without too * much difficulty. * * The base64_init() function takes the file descriptor to use when writing or * reading the encoded data, and a callback function with an argument to pass * to the callback. If the callback function is not NULL, it will be called on * every line which starts with a "-" character, with the argument, a pointer * to the current line, and the length of the current line. The callback should * return 1 if the line is an envelope start marker, 2 if the line is an * envelope end marker, 3 if the line is an envelope end marker and we should * stop here instead of searching for another block, -1 if an error occured, * or 0 if it should just be ignored. */ #include #include #include "arch.h" #include "util.h" #include "base64.h" #include "lineget.h" /* * Create and set up a Base64 state structure. Returns NULL on failure. * fd is the file descriptor to read from or write to. */ b64state *base64_init(int fd, base64_markerfunc marker, void *arg) { b64state *state; /* Try to allocate the Base64 state structure */ if(!(state = malloc(sizeof(b64state)))) return NULL; /* Set up the state parameters */ state->used = 0; state->storedbytes = 0; state->linelen = 0; state->state = 0; state->line = NULL; state->size = 0; state->pos = 0; state->ex = 0; state->fd = fd; state->marker = marker; state->arg = arg; state->decoding = 0; return state; } /* * Converts a Base64 encoded letter to a (6 bit) number between 0 and 63, or 64 * if it is a pad character, or -1 if it is anything else. */ static int b64toi(char c) { /* Decode A-B */ if((c >= 'A') && (c <= 'Z')) return(c - 'A'); /* Decode a-b */ else if((c >= 'a') && (c <= 'z')) return(c - 'a' + 26); /* Decode 0-9 */ else if((c >= '0') && (c <= '9')) return(c - '0' + 52); /* Decode the weird ones */ else if(c == '+') return 62; else if(c == '/') return 63; else if(c == '=') return 64; /* Anything else is not part of Base64 */ else return -1; } /* * Decodes up to len bytes into buf from the file descriptor associated with * the specified state structure. Returns the number of bytes it managed to * decode before running out of data or the callback function indicated that * the end of the block has been reached, or -1 on error. */ ssize_t base64_read(b64state *state, void *buf, size_t len) { u8 u, *p = buf; size_t pos = 0; int i, n, packing = 0; newblock: /* If there is a marker callback */ if(state->marker) { /* While we are not in a Base64 data block */ while(!state->decoding) { /* Get the next line; return 0 if at end of file */ i = lineget(&state->line, &state->size, &state->pos, &state->ex, state->fd); /* Return 0 if end of file */ if(!i) return 0; /* Return -1 if there was a read error */ else if(i == -1) return -1; /* If the line starts with the marker flag */ else if(state->line[0] == '-') { /* Start decoding if it is a start marker */ n = state->marker(state->arg, state->line, i); if(n == -1) return -1; else if(n == 1) state->decoding = 1; } } } else state->decoding = 1; /* Inject any stored bytes left over from the last call */ for(i = 3 - state->storedbytes; i <= 2; i++) { if(pos < len) { p[pos++] = state->dec[i]; state->storedbytes--; } } /* While we haven't decoded enough bytes */ while(pos < len) { /* If we've run out of data, read the next line */ if(state->used >= state->linelen) { if(!(i = lineget(&state->line, &state->size, &state->pos, &state->ex, state->fd))) return pos; /* Reached EOF */ else if(i == -1) return -1; /* Error */ /* If this is a marker line, call the callback */ if((state->line[0] == '-') && state->marker) { n = state->marker(state->arg, state->line, i); if(n == -1) return -1; /* Error */ else if(n == 2) { /* end marker */ state->decoding = 0; state->linelen = 0; state->storedbytes = 0; state->state = 0; packing = 0; if(pos) return pos; else goto newblock; } else if(n == 3) { /* final end marker */ state->decoding = 0; return 0; } else continue; /* Try again */ } state->linelen = i; state->used = 0; } /* Decode the current byte if it is a valid Base64 character */ if((i = b64toi(state->line[state->used++])) != -1) { u = i; /* Handle packing characters */ if(u == 64) { u = 0; packing++; } switch(state->state) { case 0: state->dec[0] = u << 2; break; case 1: state->dec[0] |= u >> 4; state->dec[1] = u << 4; break; case 2: state->dec[1] |= u >> 2; state->dec[2] = u << 6; break; case 3: state->dec[2] |= u; } /* When we've decoded a full quantum, write it out. * If there isn't enough space for all three bytes, * remember the number of bytes that wouldn't fit. */ if(++state->state >= 4) { state->state = 0; p[pos++] = state->dec[0]; if(packing >= 2) continue; if(pos == len) state->storedbytes = 2; else { p[pos++] = state->dec[1]; if(packing >= 1) continue; if(pos == len) state->storedbytes = 1; else p[pos++] = state->dec[2]; } } } } return pos; } /* * Converts a 6 bit number to a Base64 encoded letter. */ static char itob64(u8 n) { /* Encode A-B */ if(n <= 25) return('A' + n); /* Encode a-b */ else if(n <= 51) return('a' + n - 26); /* Encode 0-9 */ else if(n <= 61) return('0' + n - 52); /* Encode the weird ones */ else if(n == 62) return '+'; else if(n == 63) return '/'; /* Not a 6 bit number */ else return '?'; } /* * Flushes the output buffer if there isn't enough room for a full quantum * and a newline. */ static int b64_buf_flush(b64state *state) { if((BASE64_BUFLEN - state->used) < 6) { if(safe_write(state->fd, state->buf, state->used) != state->used) return -1; state->used = 0; } return 0; } /* * Encodes up to len bytes from buf to the file descriptor associated with * the specified state structure. Returns the number of bytes it managed to * encode, or -1 on error. */ ssize_t base64_write(b64state *state, void *buf, size_t len) { int i; size_t pos = 0; unsigned char *p = buf; /* While we haven't written all of the data */ while(pos < len) { /* Make sure there is enough space in the buffer */ if(b64_buf_flush(state) == -1) return -1; /* Encode this byte */ switch(state->state) { case 0: state->enc[0] = itob64(p[pos] >> 2); state->lastbyte = (p[pos] << 4) & 63; break; case 1: state->enc[1] = itob64(state->lastbyte | (p[pos] >> 4)); state->lastbyte = (p[pos] << 2) & 63; break; case 2: state->enc[2] = itob64(state->lastbyte | (p[pos] >> 6)); state->enc[3] = itob64(p[pos] & 63); } /* If a full quantum has been encoded */ if(++state->state == 3) { state->state = 0; /* Copy the quantum to the output buffer */ for(i = 0; i < 4; i++) state->buf[state->used++] = state->enc[i]; /* If this line is full, advance to the next one */ state->linelen += 4; if(state->linelen >= BASE64_LINELEN) { #ifdef DOS_LINE_ENDINGS state->buf[state->used++] = '\r'; #endif state->buf[state->used++] = '\n'; state->linelen = 0; } } /* Move on to the next byte */ pos++; } /* Return the amount of data written */ return pos; } /* * Flushes the write buffer associated with the specified state structure. * Returns 0 on success or -1 on error. */ int base64_flush(b64state *state) { int i; /* Make sure there is enough space in the buffer */ if(b64_buf_flush(state) == -1) return -1; /* Add packing if it's needed */ if(state->state) { switch(state->state) { case 1: state->enc[1] = itob64(state->lastbyte); state->enc[2] = '='; break; case 2: state->enc[2] = itob64(state->lastbyte); } state->enc[3] = '='; /* Copy the quantum to the output buffer */ for(i = 0; i < 4; i++) state->buf[state->used++] = state->enc[i]; state->linelen += 4; } /* Unless we're at the start of the line, advance to the next one */ if(state->linelen) { #ifdef DOS_LINE_ENDINGS state->buf[state->used++] = '\r'; #endif state->buf[state->used++] = '\n'; state->linelen = 0; } /* Write out everything that is in the buffer */ if(safe_write(state->fd, state->buf, state->used) != state->used) return -1; /* Set the pointer back the beginning of the buffer */ state->used = 0; return 0; /* Success */ } /* * Frees the specified state structure and any other associated buffers. */ void base64_destroy(b64state *state) { /* Free the line buffer */ if(state->line) free(state->line); /* Free the state structure */ free(state); }