dmc

dynamic mail client
git clone git://git.suckless.org/dmc
Log | Files | Refs | README | LICENSE

dmc.c (20345B)


      1 /* dmc - dynamic mail client
      2  * See LICENSE file for copyright and license details.
      3  */
      4 #include <stdio.h>
      5 #include <string.h>
      6 #include <dirent.h>
      7 #include <unistd.h>
      8 #include <stdlib.h>
      9 #include <stdarg.h>
     10 #include <sys/stat.h>
     11 #include <sys/wait.h>
     12 #include <sys/types.h>
     13 #include "config.h"
     14 
     15 static char *editor;
     16 static char dmcdir[128];
     17 static char **dmcaccounts();
     18 static char prompt[64];
     19 static char **acc, *defacc = NULL;
     20 static int dmc_in[2], dmc_out[2], dmc_err[2];
     21 static int dmc_pid = -1;
     22 static fd_set rfds, wfds;
     23 
     24 typedef struct {
     25 	char *out;
     26 	char *err;
     27 	int errlen;
     28 } DmcQuery;
     29 
     30 static DmcQuery reply;
     31 
     32 static void dmcstop();
     33 static int dmcsend(const char *file);
     34 static int dmcstore(const char *body);
     35 static int fexist(const char *path);
     36 
     37 #if 0
     38 static char *dmcnamegen(const char *file) {
     39 	// return filename from mail
     40 	// Name is YYYYMMDDhhmm## -- name sort by date
     41 	// if no Date..use file timestamp
     42 	// well. we just need at the end.. a unique incremental ID
     43 	// interesting mails are the last ones in mail server
     44 	return NULL;
     45 }
     46 
     47 static int dmcstore() {
     48 	// TODO
     49 	return 0;
     50 }
     51 #endif
     52 
     53 static const char* wd(const char *fmt, ...) {
     54 	va_list ap;
     55 	int len;
     56 	static char ret[512];
     57 	va_start (ap, fmt);
     58 	len = strlen (dmcdir);
     59 	memcpy (ret, dmcdir, len);
     60 	vsnprintf (ret+len, sizeof (ret)-len, fmt, ap);
     61 	va_end (ap);
     62 	return ret;
     63 }
     64 
     65 static const char *abspath(const char *file) {
     66 	static char filepath[512];
     67 	char cwd[128];
     68 	if (*file=='/')
     69 		return file;
     70 	getcwd (cwd, sizeof (cwd));
     71 	snprintf (filepath, sizeof (filepath)-strlen (file)-3, "%s", cwd);
     72 	strcat (filepath, "/");
     73 	strcat (filepath, file);
     74 	return filepath;
     75 }
     76 
     77 static const char *dmcalias(const char *filter) {
     78 	static char line[64];
     79 	FILE *fd = fopen (wd ("addrbook"), "r");
     80 	if (fd) {
     81 		for (;;) {
     82 			fgets (line, sizeof (line), fd);
     83 			if (feof (fd))
     84 				break;
     85 			if (strstr (line, filter)) {
     86 				line[strlen(line)-1] = 0;
     87 				return line;
     88 			}
     89 		}
     90 		fclose (fd);
     91 	}
     92 	return filter;
     93 }
     94 
     95 static const char *dmcmailpath(const char *name) {
     96 	static char path[256];
     97 	/* check ./%s */
     98 	if (fexist (name))
     99 		return name;
    100 	// TODO: simplify in loop and use wd()
    101 	/* check DMCDIR/box/$acc/in/%s.eml */
    102 	snprintf (path, sizeof (path), "%s/box/%s/in/%s.eml", dmcdir, defacc, name);
    103 	if (fexist (path))
    104 		return path;
    105 	/* check DMCDIR/box/$acc/in/%s */
    106 	snprintf (path, sizeof (path), "%s/box/%s/in/%s", dmcdir, defacc, name);
    107 	if (fexist (path))
    108 		return path;
    109 	/* check DMCDIR/box/$acc/%s */
    110 	snprintf (path, sizeof (path), "%s/box/%s/%s", dmcdir, defacc, name);
    111 	if (fexist (path))
    112 		return path;
    113 	/* check DMCDIR/box/%s */
    114 	snprintf (path, sizeof (path), "%s/box/%s", dmcdir, name);
    115 	if (fexist (path))
    116 		return path;
    117 	/* not found */
    118 	return NULL;
    119 }
    120 
    121 static int dmccat(const char *file) {
    122 	char line[128];
    123 	const char *f = dmcmailpath (file); // XXX dup?
    124 	if (f && fexist (f)) {
    125 		snprintf (line, sizeof (line), "cat '%s'", f);
    126 		system (line); // implement native cat
    127 		return 1;
    128 	} else fprintf (stderr, "Cannot find '%s'\n", file);
    129 	return 0;
    130 }
    131 
    132 static char *gethdr(const char *path, const char *file, const char *hdr) {
    133 	char line[1024], *ret = NULL;
    134 	FILE *fd;
    135 	int i;
    136 	snprintf (line, sizeof (line), "%s/%s", path, file);
    137 	fd = fopen (line, "r");
    138 	if (fd) {
    139 		int len = strlen (hdr);
    140 		while (!feof (fd)) {
    141 			fgets (line, sizeof (line), fd);
    142 			if (!memcmp (line, hdr, len)) {
    143 				i = strlen (line)-1;
    144 				if (i>len) {
    145 					line[i--] = '\0';
    146 					if (line[i]=='\r'||line[i]=='\n')
    147 						line[i] = '\0';
    148 					ret = strdup (line+len);
    149 				} else ret = strdup ("");
    150 				break;
    151 			}
    152 		}
    153 		fclose (fd);
    154 	}
    155 	return ret;
    156 }
    157 
    158 static int dmcls(const char *path) {
    159 	struct dirent *de;
    160 	DIR *dir;
    161 	if ((dir = opendir (path))) {
    162 		while ((de = readdir (dir))) {
    163 			if (*de->d_name!='.') {
    164 				char *subj = gethdr (path, de->d_name, "Subject: ");
    165 				char *from = gethdr (path, de->d_name, "From: ");
    166 				printf ("%s:\t%s\n\t%s\n", de->d_name, subj, from);
    167 				free (from);
    168 				free (subj);
    169 			}
    170 		}
    171 		closedir (dir);
    172 		return 1;
    173 	}
    174 	return 0;
    175 }
    176 
    177 static void dmcinit() {
    178 	char *tmp = getenv ("EDITOR");
    179 	editor = tmp? tmp: EDITOR;
    180 	if (!(tmp = getenv ("HOME"))) {
    181 		fprintf (stderr, "Cannot find HOME\n");
    182 		exit (1);
    183 	}
    184 	snprintf (dmcdir, sizeof (dmcdir), "/%s/"DMCDIR"/", tmp);
    185 	if (!fexist (dmcdir))
    186 	if (mkdir (dmcdir, DIRPERM) == -1) {
    187 		fprintf (stderr, "Cannot create \"%s\"\n", dmcdir);
    188 		exit (1);
    189 	}
    190 	mkdir (wd ("tmp"), DIRPERM);
    191 	mkdir (wd ("box"), DIRPERM);
    192 	mkdir (wd ("acc"), DIRPERM);
    193 	acc = dmcaccounts ();
    194 	defacc = acc[0];
    195 	signal (SIGINT, dmcstop);
    196 	atexit (dmcstop);
    197 	reply.out = reply.err = NULL;
    198 }
    199 
    200 static char *cfgget(const char *key) {
    201 	FILE *fd;
    202 	char line[128], *ptr, *ret = NULL;
    203 	if ((fd = fopen (wd ("acc/%s", defacc), "r"))) {
    204 		int len = strlen (key);
    205 		while (!feof (fd)) {
    206 			fgets (line, sizeof (line), fd);
    207 			if (!memcmp (line, key, len)) {
    208 				ptr = strchr (line, '#');
    209 				if (ptr) {
    210 					for (*ptr=0, ptr--; ptr != line && (*ptr==' ' || *ptr=='\t'); ptr--)
    211 						*ptr = 0;
    212 				} else line [strlen (line)-1] = 0;
    213 				ret = strdup (line+len);
    214 				break;
    215 			}
    216 		}
    217 		fclose (fd);
    218 	}
    219 	return ret;
    220 }
    221 
    222 /* server */
    223 static int dmcstart(const char *name) {
    224 	char a0[512], a1[32];
    225 	char *host, *port, *prot, *ssl;
    226 	pipe (dmc_in);
    227 	pipe (dmc_out);
    228 	pipe (dmc_err);
    229 	dmc_pid = fork ();
    230 	signal (SIGPIPE, SIG_IGN);
    231 	switch (dmc_pid) {
    232 	case -1:
    233 		fprintf (stderr, "Cannot fork\n");
    234 		exit (1);
    235 	case 0:
    236 		dup2 (dmc_in[0], 0);
    237 		close (dmc_in[0]);
    238 		close (dmc_in[1]);
    239 
    240 		dup2 (dmc_out[1], 1);
    241 		close (dmc_out[0]);
    242 		close (dmc_out[1]);
    243 
    244 		dup2 (dmc_err[1], 2);
    245 		close (dmc_err[0]);
    246 		close (dmc_err[1]);
    247 
    248 		host = cfgget ("HOST=");
    249 		port = cfgget ("PORT=");
    250 		prot = cfgget ("PROTOCOL=");
    251 		ssl = cfgget ("SSL=");
    252 		ssl = ssl? atoi (ssl)? "1": NULL: NULL;
    253 		snprintf (a0, sizeof (a0), PREFIX"/bin/dmc-%s", prot);
    254 		snprintf (a1, sizeof (a1), "dmc-%s", prot);
    255 		printf ("(%s)\n", a0);
    256 		execl (a0, a1, host, port, ssl, NULL);
    257 		free (host);
    258 		free (port);
    259 		exit (1);
    260 	default:
    261 		close (dmc_out[1]);
    262 		close (dmc_err[1]);
    263 		close (dmc_in[0]);
    264 		break;
    265 	}
    266 	return 0;
    267 }
    268 
    269 static void dmckill(int sig) {
    270 	fprintf (stderr, "Killed.\n");
    271 	kill (dmc_pid, SIGKILL);
    272 }
    273 
    274 static void dmcstop() {
    275 	if (dmc_pid != -1) {
    276 		const char *cmd = "exit\n";
    277 		write (dmc_in[1], cmd, strlen (cmd));
    278 		signal (SIGALRM, dmckill);
    279 		alarm (2);
    280 		waitpid (dmc_pid, NULL, 0);
    281 		signal (SIGALRM, NULL);
    282 		alarm (0);
    283 		dmc_pid = -1;
    284 
    285 		// duppy
    286 		close (dmc_in[0]);
    287 		close (dmc_in[1]);
    288 		close (dmc_out[0]);
    289 		close (dmc_out[1]);
    290 		close (dmc_err[0]);
    291 		close (dmc_err[1]);
    292 	}
    293 }
    294 
    295 static int dmccmd(const char *cmd) {
    296 	static char buf[128];
    297 	int ret, nfd;
    298 
    299 	if (dmc_pid == -1) {
    300 		printf ("Use 'on' or '?'\n");
    301 		return 0;
    302 	}
    303 	free (reply.out);
    304 	reply.out = NULL;
    305 	free (reply.err);
    306 	reply.err = NULL;
    307 	reply.errlen = 0;
    308 	for (;;) {
    309 		FD_ZERO (&rfds);
    310 		FD_ZERO (&wfds);
    311 		FD_SET (dmc_out[0], &rfds);
    312 		FD_SET (dmc_err[0], &rfds);
    313 		FD_SET (dmc_in[1], &wfds);
    314 		nfd = select (dmc_err[0] + 1, &rfds, &wfds, NULL, NULL);
    315 		if (nfd < 0)
    316 			break;
    317 		if (FD_ISSET (dmc_out[0], &rfds)) {
    318 			ret = read (dmc_out[0], buf, sizeof (buf)-1);
    319 			if (ret>0) {
    320 				buf[ret-1] = 0;
    321 				reply.out = strdup (buf); // XXX
    322 				//printf ("-(out)-> (%s)\n", buf);
    323 				break;
    324 			}
    325 		} else
    326 		if (FD_ISSET (dmc_err[0], &rfds)) {
    327 			ret = read (dmc_err[0], buf, sizeof (buf)-1);
    328 			if (ret>0) {
    329 				buf[ret-1] = 0;
    330 				reply.err = realloc (reply.err, reply.errlen+ret+1);
    331 				memcpy (reply.err+reply.errlen, buf, ret);
    332 				reply.errlen += ret-1;
    333 				//printf ("-(err)-> (%s)\n", buf);
    334 			}
    335 		} else
    336 		if (FD_ISSET (dmc_in[1], &wfds)) {
    337 			if (cmd && *cmd && *cmd != '\n') {
    338 				snprintf (buf, sizeof (buf), "%s\n", cmd);
    339 				write (dmc_in[1], buf, strlen (buf));
    340 				if (DEBUG) printf ("-(wri)-> (%s)\n", cmd);
    341 				cmd = NULL;
    342 			}
    343 		}
    344 	}
    345 	return 1;
    346 }
    347 
    348 static void dmcpush(const char *name) {
    349 	struct dirent *de;
    350 	char path[256], file[512];
    351 	DIR *dir;
    352 	int i;
    353 
    354 	for (i=0; acc[i]; i++) {
    355 		snprintf (path, sizeof (path), "%s/box/%s/out", dmcdir, acc[i]);
    356 		dir = opendir (path);
    357 		while ((de = readdir (dir))) {
    358 			char *n = de->d_name;
    359 			if (*n != '.' && !strstr (n, ".d")) {
    360 				snprintf (file, sizeof (file), "%s/%s", path, n);
    361 				printf ("%s: ", file);
    362 				fflush (stdout);
    363 				if (dmcsend (file)) {
    364 					snprintf (path, sizeof (path), "mv %s* '%s'",
    365 						file, wd ("box/%s/sent", defacc));
    366 					system (path); // do not use system()
    367 				}
    368 			}
    369 		}
    370 		closedir (dir);
    371 	}
    372 }
    373 
    374 static int fexist(const char *path) {
    375 	struct stat st;
    376 	int ret = path? stat (path, &st): -1;
    377 	return (ret != -1);
    378 }
    379 
    380 static int fsize(const char *path) {
    381 	FILE *fd = fopen (path, "r");
    382 	int len = 0;
    383 	if (fd) {
    384 		fseek (fd, 0, SEEK_END);
    385 		len = ftell (fd);
    386 		fclose (fd);
    387 	}
    388 	return len;
    389 }
    390 
    391 static char *parsedate(const char *str) {
    392 	static char buf[256];
    393 	char *ch;
    394 	strncpy (buf, str, sizeof (buf));
    395  	if ((ch = strchr (buf, ','))) {
    396 		int year, month, day = atoi (ch+1);
    397 		if (!day)
    398 			return 0;
    399 		ch = strchr (ch+2, ' ');
    400 		while (*ch==' ')ch++;
    401 		if (ch) {
    402 			char *q, *p = strchr (ch+3, ' ');
    403 			if (p) {
    404 				*p = 0;
    405 				printf ("MONTH (%c%c%c)\n", ch[0], ch[1], ch[2]);
    406 				month = 0; // TODO
    407 				year = atoi (p+1);
    408 				if (year) {
    409 					q = strchr (p+1, ' ');
    410 					if (q) {
    411 						int h,m,s;
    412 						*q = 0;
    413 						sscanf (p+1, "%d:%d:%d", &h, &m, &s);
    414 						snprintf (buf, sizeof (buf),
    415 							"%s/box/%s/in/%04d%02d%02d-%02d%02d%02d",
    416 							dmcdir, defacc, year, month, day, h, m, s);
    417 						/* return 0: FUCK YEAH!!1 */
    418 					} else return 0;
    419 				} else return 0;
    420 			} else return 0;
    421 		} else return 0;
    422 	} else return 0;
    423 	return buf;
    424 }
    425 
    426 // TODO: support to store only the header, not full mail
    427 static int dmcstore(const char *body) {
    428 	char dmctag[512], *file, *end, *ptr = strstr (body, "Date: "); // TODO: use gethdr..on buffer, not file
    429 	if (ptr) {
    430 		ptr = ptr + 6;
    431 		end = strchr (ptr, '\n');
    432 		// generate filename from date
    433 		file = parsedate (ptr);
    434 		if (file) {
    435 printf ("FILE IS =%s\n", file);
    436 			if (!fexist (file)) {
    437 				FILE *fd = fopen (file, "w");
    438 				if (fd) {
    439 					fputs (body, fd);
    440 					fclose (fd);
    441 					// TODO extract attachments
    442 					// TODO save only body
    443 					snprintf (dmctag, sizeof (dmctag),
    444 						"dmc-tag '%s' new `dmc-tag %s`", file, file);
    445 					system (dmctag);
    446 					return 1;
    447 				} else fprintf (stderr, "Cannot write file '%s'\n", file);
    448 			} else fprintf (stderr, "Already downloaded.\n");
    449 		} else fprintf (stderr, "Cannot generate file name for (%s)\n", body);
    450 	} else fprintf (stderr, "Cannot find date header.\n");
    451 	return 0;
    452 }
    453 
    454 static int dmcpull(int num, int lim, int dir) {
    455 	int ret, count = 0;
    456 	char cmd[64];
    457 	char *slimit = cfgget ("LIMIT=");
    458 	int limit = slimit? atoi (slimit): LIMIT;
    459 	for (;num!=lim && (!limit||count<limit);num+=dir) {
    460 		snprintf (cmd, sizeof (cmd), "cat %d\n", num);
    461 		ret = dmccmd (cmd);
    462 		printf ("RETURN IS = %d\n", ret);
    463 		ret = dmcstore (reply.err); // TODO: add support for folders
    464 		printf ("dmcstore ret = %d\n", ret);
    465 		if (!ret)
    466 			break;
    467 		count ++;
    468 	}
    469 	printf ("%d new messages\n", count);
    470 	return count;
    471 }
    472 
    473 static char **dmcaccounts() {
    474 	struct dirent *de;
    475 	static char *accounts[MAXACC];
    476 	char buf[256], *defacc = NULL;
    477 	DIR *dir;
    478 	int acc = 0;
    479 	memset (buf, 0, sizeof (buf));
    480 	if (readlink (wd ("acc.default"), buf, sizeof (buf))!=-1) {
    481 		defacc = buf + strlen (buf)-1;
    482 		while (*defacc != '/')
    483 			defacc--;
    484 		accounts[acc++] = strdup (++defacc);
    485 	} else fprintf (stderr, "No default account defined.\n");
    486 	dir = opendir (wd ("acc"));
    487 	while ((de = readdir (dir)) && acc<MAXACC-2)
    488 		if (*de->d_name != '.' && (!defacc || strcmp (de->d_name, defacc)))
    489 			accounts[acc++] = strdup (de->d_name);
    490 	closedir (dir);
    491 	accounts[acc] = NULL;
    492 	return accounts;
    493 }
    494 
    495 static void charrfree(char **ptr) {
    496 	char **p = ptr;
    497 	while (*p) {
    498 		free(*p);
    499 		p++;
    500 	}
    501 	*ptr = NULL;
    502 }
    503 
    504 static int dmcsend(const char *file) {
    505 	char line[512];
    506 	char *user = cfgget ("USER=");
    507 	char *mail = cfgget ("MAIL=");
    508 	char *to = gethdr ((*file=='/')?"/":"./", file, "To: ");
    509 	/* TODO: Implement non-msmtp method */
    510 	snprintf (line, sizeof (line),
    511 		"dmc-pack `ls %s.d/* 2>/dev/null` < %s "
    512 		"| msmtp --user=\"%s\" --from=\"%s\" \"%s\"",
    513 		file, file, user, mail, to);
    514 	if (system (line) != 0) {
    515 		fprintf (stderr, "Error ocurred while sending %s\n", file);
    516 		return 0;
    517 	} else fprintf (stderr, "Sent.\n");
    518 	return 1;
    519 }
    520 
    521 static int dmcline(const char *line) {
    522 	int ret = 1;
    523 	char cmd[128];
    524 	if (!strcmp (line, "?")) {
    525 		printf ("Usage: on off push pull exit ls lsd cat ..\n");
    526 	} else
    527 	if (!strcmp (line, "login")) {
    528 		char *user = cfgget ("USER=");
    529 		char *pass = cfgget ("PASS=");
    530 		snprintf (cmd, sizeof (cmd), "login %s %s\n", user, pass);
    531 		if (!dmccmd (cmd))
    532 			printf ("Error.\n");
    533 		free (user);
    534 		free (pass);
    535 	} else
    536 	if (!strcmp (line, "on")) {
    537 		dmcstart (acc[0]);
    538 		if (!dmccmd (NULL))
    539 			printf ("Error.\n");
    540 	} else
    541 	if (!memcmp (line, "get ", 4)) {
    542 		snprintf (cmd, sizeof (cmd), "cat %s", line+4);
    543 		if (dmc_pid != -1) {
    544 			dmccmd (cmd);
    545 			printf ("CHECK (%s)\n", reply.out);
    546 			// TODO. if (check)..
    547 			dmcstore (reply.err);
    548 		} else fprintf (stderr, "Not connected.\n");
    549 	} else
    550 	if (!strcmp (line, "off")) {
    551 		dmcstop ();
    552 	} else
    553 	if (!strcmp (line, "ls")) {
    554 		if (dmc_pid != -1)
    555 			dmccmd ("ls\n");
    556 		else dmcls (wd ("box/%s/in", defacc));
    557 	} else
    558 	if (!memcmp (line, "cat ", 4)) {
    559 		if (dmc_pid != -1)
    560 			dmccmd (line); // bypass
    561 		else dmccat (line+4);
    562 	} else
    563 	if (!strcmp (line, "push")) {
    564 		dmcpush (defacc);
    565 	} else
    566 	if (!strcmp (line, "pull")) {
    567 		dmcpull (5, 9, 1); // pop3 test
    568 	} else
    569 	if (!strcmp (line, "exit")) {
    570 		return 0;
    571 	} else {
    572 		/* bypass to child */
    573 		if (!dmccmd (line)) {
    574 			fprintf (stderr, "## No reply\n");
    575 			ret = 0;
    576 		}
    577 	}
    578 	if (ret) {
    579 		printf ("-(out)-> %s\n", reply.out);
    580 		//printf ("-(err)-> %s\n", reply.err);
    581 	}
    582 	return 1;
    583 }
    584 
    585 static int usage(const char *argv0, int lon) {
    586 	fprintf (stderr, "Usage: %s [-hv] [-c [cmd] [-s file] [-e acc] [-A file ..]\n"
    587 		"\t[-a addr] [-m [a [s [..]] [-f m a] [-r m a] [-l [box]]\n", argv0);
    588 	if (lon) fprintf (stderr,
    589 		" -m [a [s]..] create mail [addr [subj [file1 file2 ..]]]\n"
    590 		" -c [cmd]     command shell\n"
    591 		" -e [acc]     list/edit/set default accounts\n"
    592 		" -a [addr]    grep in addressbook\n"
    593 		" -A [file .]  attach files to ~/.dmc/mail.last done by 'dmc -m'\n"
    594 		" -l [box]     list mails in specified box of def account\n"
    595 		" -s [file]    send mail\n"
    596 		" -f m [addr]  forward mail to addr\n"
    597 		" -r m [addr]  reply mail to addr\n"
    598 		" -v           show version\n"
    599 		"");
    600 	return 0;
    601 }
    602 
    603 static int dmcmail(const char *addr, const char *subj, const char *slurp, const char *slurptitle) {
    604 	const char *from = cfgget("MAIL=");
    605 	char file[128], line[128];
    606 	int fd, ret = 0;
    607 	snprintf (file, sizeof (file), "%s/box/%s/out/mail.XXXXXX", dmcdir, acc[0]);
    608 	fd = mkstemp (file);
    609 	if (fd != -1) {
    610 		// TODO: fchmod or mkostemp
    611 		snprintf (line, sizeof (line),
    612 			"X-Mailer: dmc v"VERSION"\n"
    613 			"From: %s\n"
    614 			"To: %s\n"
    615 			"Subject: %s\n\n\n", from, addr, subj);
    616 		write (fd, line, strlen (line));
    617 		if (slurp) {
    618 			FILE *sfd = fopen (slurp, "r");
    619 			if (sfd) {
    620 				int body = 0;
    621 				write (fd, "\n\n", 2);
    622 				if (slurptitle)
    623 					write (fd, slurptitle, strlen (slurptitle));
    624 				for (;;) {
    625 					fgets (line, sizeof (line), sfd);
    626 					if (feof (sfd)) break;
    627 					if (body) {
    628 						write (fd, "> ", 2);
    629 						write (fd, line, strlen (line));
    630 					} else if (strlen (line)<4) body = 1;
    631 				}
    632 			}
    633 		}
    634 		close (fd);
    635 		snprintf (line, sizeof (line), EDITOR" '%s'", file);
    636 		system (line);
    637 		if (fsize (file)<32) {
    638 			fprintf (stderr, "Aborted\n");
    639 			unlink (file);
    640 		} else {
    641 			snprintf (line, sizeof (line), "%s/mail.last", dmcdir);
    642 			unlink (line);
    643 			symlink (file, line);
    644 			ret = 1;
    645 		}
    646 	} else fprintf (stderr, "Cannot create '%s'\n", file);
    647 	return ret;
    648 }
    649 
    650 static void dmcattach(const char *file) {
    651 	char path[256];
    652 	const char *name = file + strlen (file)-1;
    653 	while (*name!='/' && name>file)
    654 		name--;
    655 	name++;
    656 	path[0] = 0;
    657 	memset (path, 0, sizeof(path));
    658 	if (readlink (wd ("mail.last"), path, sizeof (path)-strlen (name)-2)!=-1) {
    659 		strcat (path, ".d/");
    660 		mkdir (path, 0750);
    661 		strcat (path, name);
    662 		symlink (abspath (file), path);
    663 	} else fprintf (stderr, "Cannot attach '%s'\n", file);
    664 }
    665 
    666 static char *evalstr(const char *mail, const char *str) {
    667 	int outi = 0;
    668 	char *out, *tmp;
    669 	const char *ptr = str;
    670 	out = malloc (1024); // XXX overflow
    671 	while (*ptr) {
    672 		if (*ptr == '{') {
    673 			int dsti = 0;
    674 			char dst[1024];
    675 			ptr++;
    676 			while (*ptr && *ptr != '}' && dsti<sizeof(dst)-1) {
    677 				dst[dsti++] = *ptr;
    678 				ptr++;
    679 			}
    680 			dst[dsti] = 0;
    681 			tmp = gethdr ("/", mail, dst);
    682 			if (tmp) {
    683 				strcpy (out+outi, tmp);
    684 				outi += strlen (tmp);
    685 				free (tmp);
    686 			}
    687 			ptr++;
    688 			continue;
    689 		}
    690 		out[outi++] = *ptr;
    691 		ptr++;
    692 	}
    693 	out[outi] = 0;
    694 	return out;
    695 }
    696 
    697 static void dmcfwd(const char *file, const char *addr, const char *msg, const char *sub) {
    698 	if (file) {
    699 		char *m, *subj, *msub = gethdr ("/", file, "Subject: ");
    700 		if (msub) {
    701 			char *s = evalstr (file, sub);
    702 			subj = malloc (strlen (msub) + strlen (s) + 2);
    703 			strcpy (subj, s);
    704 			strcat (subj, msub);
    705 			free (s);
    706 		} else {
    707 			subj = strdup (sub);
    708 		}
    709 		if (addr && *addr)
    710 			addr = dmcalias (addr);
    711 		m = evalstr (file, msg);
    712 		dmcmail (addr, subj, file, m);
    713 		free (m);
    714 		free (subj);
    715 	} else fprintf (stderr, "Mail not found.\n");
    716 }
    717 
    718 int main(int argc, char **argv) {
    719 	char file[128], line[128];
    720 	int i;
    721 
    722 	if (argc < 2)
    723 		return usage (argv[0], 0);
    724 
    725 	dmcinit ();
    726 	if (strcmp (argv[1], "-e")) {
    727 		if (!acc[0]) {
    728 			fprintf (stderr, "Use 'dmc -e' to configure the account\n");
    729 			return 1;
    730 		}
    731 	}
    732 
    733 	if (argv[1][0] == '-')
    734 	switch (argv[1][1]) {
    735 	case 'l':
    736 		if (argc>2) {
    737 			if (dmccat (argv[2]))
    738 				break;
    739 			snprintf (line, sizeof (line), "%s/box/%s", dmcdir, argv[2]);
    740 		} else snprintf (line, sizeof (line), "%s/box/%s/in", dmcdir, acc[0]);
    741 		dmcls (line);
    742 		break;
    743 	case 'A':
    744 		for (i=2; i<argc; i++)
    745 			dmcattach (argv[i]);
    746 		break;
    747 	case 'v':
    748 		printf ("dmc v"VERSION"\n");
    749 		break;
    750 	case 's':
    751 		if (argc>2)
    752 			dmcsend (argv[2]);
    753 		else fprintf (stderr, "Usage: dmc-cmd [-s file]\n");
    754 		break;
    755 	case 'm':
    756 		{
    757 			const char *addr = "";
    758 			const char *subj = "";
    759 			if (argc>2)
    760 				addr = argv[2];
    761 			if (argc>3)
    762 				subj = argv[3];
    763 			if (addr && !strchr (addr, '@'))
    764 				addr = dmcalias (addr);
    765 			if (dmcmail (addr, subj, NULL, NULL))
    766 				for (i=4; argv[i]; i++)
    767 					dmcattach (argv[i]);
    768 		}
    769 		break;
    770 	case 'e':
    771 		if (argc<3) {
    772 			for (i=0; acc[i]; i++)
    773 				printf ("%s\n", acc[i]);
    774 		} else {
    775 			snprintf (file, sizeof (file), "%s/acc/%s", dmcdir, argv[2]);
    776 			if (!fexist (file)) {
    777 				/* create template */
    778 				FILE *fd = fopen (file, "w");
    779 				if (fd) {
    780 					fprintf (fd,
    781 						"LIMIT=50 # get only 50 mails for each folder\n"
    782 						"PROTOCOL=pop3 # imap4\n"
    783 						"HOST=serverhost.com\n"
    784 						"PORT=110\n"
    785 						"SSL=0\n"
    786 						"#SEND=acc-name\n"
    787 						"SEND=!msmtp\n"
    788 						"MAIL=username@domain.com\n"
    789 						"USER=username\n"
    790 						"PASS=password\n");
    791 					fclose (fd);
    792 				} else {
    793 					fprintf (stderr, "Cannot write in %s\n", file);
    794 					return 1;
    795 				}
    796 			}
    797 			snprintf (line, sizeof (line), EDITOR" %s", file);
    798 			system (line);
    799 			if (fsize(file)<32) {
    800 				printf ("Abort\n");
    801 				unlink (file);
    802 			} else {
    803 				printf ("Default account has changed.\n");
    804 				unlink (wd ("acc.default"));
    805 				symlink (file, wd ("acc.default"));
    806 				mkdir (wd ("box/%s", argv[2]), DIRPERM);
    807 				mkdir (wd ("box/%s/out", argv[2]), DIRPERM);
    808 				mkdir (wd ("box/%s/in", argv[2]), DIRPERM);
    809 				mkdir (wd ("box/%s/sent", argv[2]), DIRPERM);
    810 			}
    811 		}
    812 		break;
    813 	case 'a':
    814 		if (argc<3) {
    815 			snprintf (line, sizeof (line), "%s '%s'", editor, wd ("addrbook"));
    816 			system (line);
    817 		} else {
    818 			const char *mail = dmcalias (argv[2]);
    819 			if (mail)
    820 				puts (mail);
    821 		}
    822 		break;
    823 	case 'c':
    824 		if (argc<3) {
    825 			strcpy (prompt, "dmc> ");
    826 			do {
    827 				write (1, prompt, strlen (prompt));
    828 				fgets(line, sizeof (line), stdin);
    829 				if (feof (stdin))
    830 					strcpy (line, "exit");
    831 				else line[strlen (line)-1] = '\0';
    832 			} while (dmcline (line));
    833 		} else {
    834 			strcpy (prompt, "");
    835 			for (i=2; i<argc; i++)
    836 				dmcline (argv[i]);
    837 		}
    838 		dmcstop ();
    839 		break;
    840 	case 'r':
    841 		dmcfwd (dmcmailpath (argv[2]), argc>3?argv[3]:"", REPMSG, REPSUB);
    842 		break;
    843 	case 'f':
    844 		dmcfwd (dmcmailpath (argv[2]), argc>3?argv[3]:"", FWDMSG, FWDSUB);
    845 		break;
    846 	default:
    847 		return usage (argv[0], 1);
    848 	}
    849 
    850 	charrfree (acc);
    851 	return 0;
    852 }