1/*
2 * Copyright 2011-2013, Axel Dörfler, axeld@pinc-software.de.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7#include <stdlib.h>
8
9#include "Protocol.h"
10#include "Response.h"
11
12#include "argv.h"
13
14
15struct cmd_entry {
16	const char*	name;
17	void		(*func)(int argc, char **argv);
18	const char*	help;
19};
20
21
22static void do_help(int argc, char** argv);
23
24
25extern const char* __progname;
26static const char* kProgramName = __progname;
27
28static IMAP::Protocol sProtocol;
29
30
31static void
32error(const char* context, status_t status)
33{
34	fprintf(stderr, "Error during %s: %s\n", context, strerror(status));
35}
36
37
38static void
39usage()
40{
41	printf("Usage: %s <server> <username> <password>\n", kProgramName);
42	exit(1);
43}
44
45
46// #pragma mark -
47
48
49static void
50do_select(int argc, char** argv)
51{
52	const char* folder = "INBOX";
53	if (argc > 1)
54		folder = argv[1];
55
56	IMAP::SelectCommand command(folder);
57	status_t status = sProtocol.ProcessCommand(command);
58	if (status == B_OK) {
59		printf("Next UID: %" B_PRIu32 ", UID validity: %" B_PRIu32 "\n",
60			command.NextUID(), command.UIDValidity());
61	} else
62		error("select", status);
63}
64
65
66static void
67do_folders(int argc, char** argv)
68{
69	IMAP::FolderList folders;
70	BString separator;
71
72	status_t status = sProtocol.GetFolders(folders, separator);
73	if (status != B_OK) {
74		error("folders", status);
75		return;
76	}
77
78	for (size_t i = 0; i < folders.size(); i++) {
79		printf(" %s %s\n", folders[i].subscribed ? "*" : " ",
80			folders[i].folder.String());
81	}
82}
83
84
85static void
86do_fetch(int argc, char** argv)
87{
88	uint32 from = 1;
89	uint32 to;
90	uint32 flags = IMAP::kFetchAll;
91	if (argc < 2) {
92		printf("usage: %s [<from>] [<to>] [header|body]\n", argv[0]);
93		return;
94	}
95	if (argc > 2) {
96		if (!strcasecmp(argv[argc - 1], "header")) {
97			flags = IMAP::kFetchHeader;
98			argc--;
99		} else if (!strcasecmp(argv[argc - 1], "body")) {
100			flags = IMAP::kFetchBody;
101			argc--;
102		}
103	}
104	if (argc > 2) {
105		from = atoul(argv[1]);
106		to = atoul(argv[2]);
107	} else
108		from = to = atoul(argv[1]);
109
110	IMAP::FetchCommand command(from, to, flags | IMAP::kFetchFlags);
111
112	// A fetch listener that dumps everything to stdout
113	class Listener : public IMAP::FetchListener {
114	public:
115		virtual ~Listener()
116		{
117		}
118
119		virtual	bool FetchData(uint32 fetchFlags, BDataIO& stream,
120			size_t& length)
121		{
122			fBuffer.SetSize(0);
123
124			char buffer[65535];
125			while (length > 0) {
126				ssize_t bytesRead = stream.Read(buffer,
127					min_c(sizeof(buffer), length));
128				if (bytesRead <= 0)
129					break;
130
131				fBuffer.Write(buffer, bytesRead);
132				length -= bytesRead;
133			}
134
135			// Null terminate the buffer
136			char null = '\0';
137			fBuffer.Write(&null, 1);
138
139			return true;
140		}
141
142		virtual	void FetchedData(uint32 fetchFlags, uint32 uid, uint32 flags)
143		{
144			printf("================= UID %ld, flags %lx =================\n",
145				uid, flags);
146			puts((const char*)fBuffer.Buffer());
147		}
148
149	private:
150		BMallocIO	fBuffer;
151	} listener;
152
153	command.SetListener(&listener);
154
155	status_t status = sProtocol.ProcessCommand(command);
156	if (status != B_OK) {
157		error("fetch", status);
158		return;
159	}
160}
161
162
163static void
164do_flags(int argc, char** argv)
165{
166	uint32 from = 1;
167	uint32 to;
168	if (argc < 2) {
169		printf("usage: %s [<from>] [<to>]\n", argv[0]);
170		return;
171	}
172	if (argc > 2) {
173		from = atoul(argv[1]);
174		to = atoul(argv[2]);
175	} else
176		to = atoul(argv[1]);
177
178	IMAP::MessageEntryList entries;
179	IMAP::FetchMessageEntriesCommand command(entries, from, to, true);
180	status_t status = sProtocol.ProcessCommand(command);
181	if (status != B_OK) {
182		error("flags", status);
183		return;
184	}
185
186	for (size_t i = 0; i < entries.size(); i++) {
187		printf("%10lu %8lu bytes, flags: %#lx\n", entries[i].uid,
188			entries[i].size, entries[i].flags);
189	}
190}
191
192
193static void
194do_noop(int argc, char** argv)
195{
196	IMAP::RawCommand command("NOOP");
197	status_t status = sProtocol.ProcessCommand(command);
198	if (status != B_OK)
199		error("noop", status);
200}
201
202
203static void
204do_raw(int argc, char** argv)
205{
206	// build command back again
207	char command[4096];
208	command[0] = '\0';
209
210	for (int i = 1; i < argc; i++) {
211		if (i > 1)
212			strlcat(command, " ", sizeof(command));
213		strlcat(command, argv[i], sizeof(command));
214	}
215
216	class RawCommand : public IMAP::Command, public IMAP::Handler {
217	public:
218		RawCommand(const char* command)
219			:
220			fCommand(command)
221		{
222		}
223
224		BString CommandString()
225		{
226			return fCommand;
227		}
228
229		bool HandleUntagged(IMAP::Response& response)
230		{
231			if (response.IsCommand(fCommand)) {
232				printf("-> %s\n", response.ToString().String());
233				return true;
234			}
235
236			return false;
237		}
238
239	private:
240		const char* fCommand;
241	};
242	RawCommand rawCommand(command);
243	status_t status = sProtocol.ProcessCommand(rawCommand);
244	if (status != B_OK)
245		error("raw", status);
246}
247
248
249static cmd_entry sBuiltinCommands[] = {
250	{"select", do_select, "Selects a mailbox, defaults to INBOX"},
251	{"folders", do_folders, "List of existing folders"},
252	{"flags", do_flags,
253		"List of all mail UIDs in the mailbox with their flags"},
254	{"fetch", do_fetch,
255		"Fetch mails via UIDs"},
256	{"noop", do_noop, "Issue a NOOP command (will report new messages)"},
257	{"raw", do_raw, "Issue a raw command to the server"},
258	{"help", do_help, "prints this help text"},
259	{"quit", NULL, "exits the application"},
260	{NULL, NULL, NULL},
261};
262
263
264static void
265do_help(int argc, char** argv)
266{
267	printf("Available commands:\n");
268
269	for (cmd_entry* command = sBuiltinCommands; command->name != NULL;
270			command++) {
271		printf("%8s - %s\n", command->name, command->help);
272	}
273}
274
275
276// #pragma mark -
277
278
279int
280main(int argc, char** argv)
281{
282	if (argc < 4)
283		usage();
284
285	const char* server = argv[1];
286	const char* user = argv[2];
287	const char* password = argv[3];
288	bool useSSL = argc > 4;
289	uint16 port = useSSL ? 993 : 143;
290
291	BNetworkAddress address(AF_INET, server, port);
292	printf("Connecting to \"%s\" as %s%s, port %u\n", server, user,
293		useSSL ? " with SSL" : "", address.Port());
294
295	status_t status = sProtocol.Connect(address, user, password, useSSL);
296	if (status != B_OK) {
297		error("connect", status);
298		return 1;
299	}
300
301	while (true) {
302		printf("> ");
303		fflush(stdout);
304
305		char line[1024];
306		if (fgets(line, sizeof(line), stdin) == NULL)
307			break;
308
309        argc = 0;
310        argv = build_argv(line, &argc);
311        if (argv == NULL || argc == 0)
312            continue;
313
314		if (!strcmp(argv[0], "quit")
315			|| !strcmp(argv[0], "exit")
316			|| !strcmp(argv[0], "q"))
317			break;
318
319        int length = strlen(argv[0]);
320		bool found = false;
321
322		for (cmd_entry* command = sBuiltinCommands; command->name != NULL;
323				command++) {
324			if (!strncmp(command->name, argv[0], length)) {
325				command->func(argc, argv);
326				found = true;
327				break;
328			}
329		}
330
331		if (!found) {
332			fprintf(stderr, "Unknown command \"%s\". Type \"help\" for a "
333				"list of commands.\n", argv[0]);
334		}
335
336		free(argv);
337	}
338
339
340	return 0;
341}
342