Load configuration files for pads.
[easyotp.git] / libotp.c
blob95c9e017935d4cf16c688d0c913995d5509e49a7
1 /** Practical One-time Pad Library
3 * Created:20080514
4 * By Jeff Connelly
5 */
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <sysexits.h>
10 #include <string.h>
12 #include "libotp.h"
13 #include "base64.h"
15 PAD *pads = NULL;
17 /** Show a list of all loaded pads. */
18 void show_pads()
20 PAD *p;
22 for (p = pads; p; p = p->next) {
23 printf("Pad: %s: %s\n", p->name, p->local_filename);
27 /** Open a companion offset file for given pad. Caller must close.
29 * @param mode Either "rt" or "wt", for reading or writing.
31 FILE *open_offset_file(PAD *p, char *mode)
33 FILE *ofp;
34 char *offset_filename;
35 size_t filename_length;
37 /* Offset is stored in a separate file; pad plus OFFSET_FILE_NAME_EXTENSION. */
38 filename_length = strlen(p->local_filename) + strlen(OFFSET_FILE_EXTENSION) + 1;
39 offset_filename = malloc(filename_length);
40 if (!offset_filename) {
41 perror("malloc");
42 exit(EX_UNAVAILABLE);
44 snprintf(offset_filename, filename_length, "%s" OFFSET_FILE_EXTENSION, p->local_filename);
46 /* Read offset from file. */
47 ofp = fopen(offset_filename, mode);
48 if (!ofp) {
49 fprintf(stderr, "opening offset file %s failed\n", offset_filename);
50 perror("fopen");
51 free(offset_filename);
52 exit(EX_IOERR);
54 free(offset_filename);
56 return ofp;
59 /** Read the pad offset of a given pad. */
60 unsigned long read_offset(PAD *p)
62 FILE *ofp;
63 char buffer[OFFSET_SIZE];
64 unsigned long offset;
66 ofp = open_offset_file(p, "rt");
68 memset(buffer, 0, OFFSET_SIZE);
69 if (fread(buffer, 1, OFFSET_SIZE - 1, ofp) < 1) {
70 fprintf(stderr, "could not read offset file for %s\n", p->local_filename);
71 exit(EX_IOERR);
74 if (fclose(ofp) != 0) {
75 fprintf(stderr, "error closing offset file for reading for %s\n", p->local_filename);
76 perror("fclose");
77 exit(EX_IOERR);
80 /* We finally got it! */
81 offset = strtoul(buffer, NULL, 10);
83 return offset;
86 /** Write the pad offset to the given pad. */
87 void write_offset(PAD *p, unsigned long offset)
89 FILE *ofp;
90 char buffer[OFFSET_SIZE];
92 ofp = open_offset_file(p, "wt");
94 memset(buffer, 0, OFFSET_SIZE);
95 snprintf(buffer, OFFSET_SIZE - 1, "%ld", offset);
96 printf("buffer=%s\n", buffer);
97 if (fwrite(buffer, strlen(buffer), 1, ofp) != 1) {
98 fprintf(stderr, "write error saving offset %ld for %s\n", offset, p->local_filename);
99 exit(EX_IOERR);
103 if (fclose(ofp) != 0) {
104 fprintf(stderr, "error closing offset file for writing for %s\n", p->local_filename);
105 perror("fclose");
106 exit(EX_IOERR);
110 /** Load a pad file from disk, adding to 'pads' global. */
111 void load_pad(char *local_filename, char *pad_name)
113 FILE *fp;
114 PAD *new_pad;
116 fp = fopen("/Volumes/Not Backed Up/otp/otp-dazzlement", "rb");
117 if (!fp) {
118 perror("fopen");
119 exit(EXIT_FAILURE);
122 new_pad = malloc(sizeof(PAD));
123 if (!new_pad) {
124 perror("malloc");
125 exit(EX_UNAVAILABLE);
128 new_pad->local_filename = strdup(local_filename);
129 new_pad->name = strdup(pad_name);
130 new_pad->fp = fp;
131 new_pad->next = NULL;
133 /* Add to linked list. */
134 if (!pads) {
135 pads = new_pad;
136 } else {
137 PAD *p, *tail;
139 /* Find tail */
140 for (p = pads; p; p = p->next)
141 tail = p;
142 tail->next = new_pad;
146 /** Read a line from a file, up to max characters.
147 * Does not place \n in line string.
149 * Based on http://www.eskimo.com/~scs/cclass/notes/sx6c.html
151 * @return Line length (0 for empty), or EOF for end-of-file.
153 static int getline(FILE *fp, char *line, unsigned int max)
155 int i, c;
157 i = 0;
158 max -= 1;
160 while ((c = fgetc(fp)) != EOF) {
161 if (c == '\n')
162 break;
163 if (i < max)
164 line[i++] = c;
168 if (c == EOF && i == 0)
169 return EOF;
171 line[i] = '\0';
172 return i;
175 void load_config(char *config_filename)
177 FILE *cfg;
178 char line[MAX_CONFIG_LINE];
180 cfg = fopen(config_filename, "rt");
181 if (!cfg) {
182 fprintf(stderr, "failed to open configuration file %s\n", config_filename);
183 perror("fopen config file");
184 exit(EX_IOERR);
187 while(getline(cfg, line, MAX_CONFIG_LINE) != EOF) {
188 char *pad_name, *pad_filename;
190 /* Line is: (pad name)=(local filename) */
191 pad_name = strtok(line, "=");
192 if (!pad_name)
193 continue;
194 pad_filename = strtok(NULL, "=");
196 /* TODO: separate send and recv filename, separated with : */
197 printf("pad=|%s|, filename=|%s|\n", pad_name, pad_filename);
199 load_pad(pad_filename, pad_name);
203 /** Find pad with given name. */
204 PAD *find_pad(char *pad_name)
206 PAD *p;
208 /* Null or blank pad = default (first) pad. */
209 if (!pad_name || !strcmp(pad_name, ""))
210 return pads;
212 for (p = pads; p; p = p->next) {
213 if (!strcmp(p->name, pad_name))
214 return p;
217 return NULL;
220 /** Close all pads and free allocated memory. */
221 void free_pads()
223 PAD *p, *next;
225 for (p = pads; p; p = next) {
226 free(p->name);
227 free(p->local_filename);
228 fclose(p->fp);
229 next = p->next;
230 free(p);
234 void free_message(MESSAGE *msg)
236 free(msg->cipher_text);
237 free(msg);
240 /** Unpackage a message packaged for transport.
242 * Caller must free_message(). */
243 MESSAGE *unpackage(char *input)
245 MESSAGE *msg;
246 unsigned int at, i;
247 char *s, *end, *b64_ct;
248 char pad_name[PAD_NAME_LENGTH];
250 msg = malloc(sizeof(MESSAGE));
251 if (!msg) {
252 perror("malloc");
253 exit(EX_UNAVAILABLE);
256 /** Format <v0.7 (pad name is unspecified; use default):
257 * MARKER_BEGIN + offset + comma
258 * base64'd data
259 * MARKER_END
261 * Format >=0.7:
262 * MARKER_BEGIN + offset + comma + pad_name + comma
263 * base64'd data
264 * MARKER_END
267 at = 0;
269 /* Locate where the message begins. */
270 s = strstr(input, MARKER_BEGIN);
271 if (!s) {
272 fprintf(stderr, "unpackage: input |%s| lacks beginning marker %s\n",
273 input, MARKER_BEGIN);
274 exit(EX_DATAERR);
277 /* ...and ends. */
278 end = strstr(input, MARKER_END);
279 if (!end) {
280 fprintf(stderr, "unpackage: input |%s| lacks ending marker %s\n",
281 input, MARKER_END);
282 exit(EX_DATAERR);
285 s += strlen(MARKER_BEGIN);
286 msg->offset = strtoul(s, &s, 10);
288 printf("offset=%ld\n", msg->offset);
290 /* Move after mandatory comma. */
291 if (s[0] != ',') {
292 fprintf(stderr, "unpackage: missing comma after offset, at |%s|, input |%s|\n",
293 s, input);
294 exit(EX_DATAERR);
296 ++s;
298 memset(pad_name, 0, PAD_NAME_LENGTH);
300 /* Includes pad name? */
301 if (s[0] != '\n' && s[0] != '\r') {
302 unsigned int i;
304 i = 0;
305 /* v0.7+ message, includes pad name */
306 while(s[0] != '\n' && s[0] != '\r' && s[0] != ',' && s < end) {
307 pad_name[i] = s[0];
308 ++s;
309 ++i;
310 if (i > PAD_NAME_LENGTH) {
311 fprintf(stderr, "unpackage: pad name length > maximum %d, in input |%s|\n",
312 PAD_NAME_LENGTH, input);
313 exit(EX_DATAERR);
318 printf("Pad name: |%s|\n", pad_name);
320 msg->pad = find_pad(pad_name);
321 if (!msg->pad) {
322 fprintf(stderr, "No such pad by name '%s'\n", pad_name);
323 exit(EX_DATAERR);
326 /* Go to next line */
327 while((s[0] == '\n' || s[0] == '\r' || s[0] == ',') && (s < end))
328 ++s;
330 printf("s=%s\n", s);
332 /* Extract base64 data from end of message. */
333 b64_ct = strdup(s);
334 b64_ct[end - s] = 0;
336 printf("b64_data=<%s>\n", b64_ct);
338 /* Decode base64 */
339 msg->cipher_text = malloc(strlen(b64_ct));
340 msg->length = base64_decode(b64_ct, msg->cipher_text);
341 free(b64_ct);
343 printf("decoded to %ld bytes\n", msg->length);
345 return msg;
348 /** Decrypt a packaged message and return plaintext.
350 * @param out Pointer to pointer which is set to the output. Caller must free.
351 * There is an extra null at the end of this buffer, not part of the output,
352 * but there may be embedded nulls if binary data is encoded. If this is expected,
353 * use the return value:
355 * @return Length of message, in bytes.
357 unsigned int otp_decrypt(char *input, char **out)
359 MESSAGE *msg;
360 unsigned int length;
362 msg = unpackage(input);
363 length = msg->length;
364 *out = otp_decrypt_msg(msg);
365 free_message(msg);
367 return length;
370 /** Deccrypt an encrypted message.
372 * @return Decrypted message, of length msg->length. Caller frees.
374 char *otp_decrypt_msg(MESSAGE *msg)
376 char *pad_data, *out;
377 unsigned int i;
379 /* Seek to area of pad we're using. */
380 if (fseek(msg->pad->fp, msg->offset, SEEK_SET) < 0) {
381 fprintf(stderr, "failed to seek in pad %s to %ld\n",
382 msg->pad->name, msg->offset);
383 perror("fseek");
384 exit(EXIT_FAILURE);
387 /* Read pad. */
388 pad_data = malloc(msg->length);
389 if (!pad_data) {
390 perror("malloc pad data");
391 exit(EX_UNAVAILABLE);
393 if (fread(pad_data, msg->length, 1, msg->pad->fp) < 1) {
394 fprintf(stderr, "read pad %s, offset %ld, length %ld failed",
395 msg->pad->name, msg->offset, msg->length);
396 exit(EXIT_FAILURE);
399 /* Apply XOR to give output. */
400 out = malloc(msg->length + 1);
401 if (!out) {
402 perror("malloc output buffer");
403 exit(EX_UNAVAILABLE);
406 for (i = 0; i < msg->length; ++i) {
407 out[i] = pad_data[i] ^ msg->cipher_text[i];
410 /* Null-terminate output for convenience when using text-only messages.
411 * The output is still of length msg->length; use msg->length when decrypting
412 * binary messages. */
413 out[msg->length + 1] = 0;
415 free(pad_data);
417 return out;