1 /* $OpenBSD: protocol.c,v 1.4 2005/04/13 02:33:09 deraadt Exp $ */
2 
3 /*
4  * POP protocol handling.
5  */
6 
7 #include <stdio.h>
8 #include <unistd.h>
9 #include <stdlib.h>
10 #include <signal.h>
11 #include <setjmp.h>
12 #include <string.h>
13 #include <stdarg.h>
14 #include <errno.h>
15 
16 #include "misc.h"
17 #include "params.h"
18 #include "protocol.h"
19 
20 struct pop_buffer pop_buffer;
21 static sigjmp_buf pop_timed_out;
22 
pop_init(void)23 void pop_init(void)
24 {
25 	pop_buffer.ptr = pop_buffer.size = 0;
26 }
27 
pop_clean(void)28 void pop_clean(void)
29 {
30 	memset(pop_buffer.data, 0, pop_buffer.ptr);
31 	memmove(pop_buffer.data, &pop_buffer.data[pop_buffer.ptr],
32 		pop_buffer.size -= pop_buffer.ptr);
33 	pop_buffer.ptr = 0;
34 }
35 
pop_sane(void)36 int pop_sane(void)
37 {
38 	return pop_buffer.size <= sizeof(pop_buffer.data) &&
39 	    pop_buffer.ptr <= pop_buffer.size;
40 }
41 
pop_timeout(int signum)42 static void pop_timeout(int signum)
43 {
44 	signal(SIGALRM, SIG_DFL);
45 	siglongjmp(pop_timed_out, 1);
46 }
47 
pop_fetch(void)48 static void pop_fetch(void)
49 {
50 	int size;
51 
52 	signal(SIGALRM, pop_timeout);
53 	alarm(POP_TIMEOUT);
54 
55 	size = read(STDIN_FILENO, pop_buffer.data, sizeof(pop_buffer.data));
56 
57 	alarm(0);
58 	signal(SIGALRM, SIG_DFL);
59 
60 	pop_buffer.ptr = 0;
61 	pop_buffer.size = (size >= 0) ? size : 0;
62 }
63 
pop_get_char(void)64 static int pop_get_char(void)
65 {
66 	if (pop_buffer.ptr >= pop_buffer.size) {
67 		pop_fetch();
68 		if (!pop_buffer.size) return -1;
69 	}
70 
71 	return (unsigned char)pop_buffer.data[pop_buffer.ptr++];
72 }
73 
pop_get_line(char * line,int size)74 static char *pop_get_line(char *line, int size)
75 {
76 	int pos;
77 	int seen_cr, seen_nul;
78 	int c;
79 
80 	pos = 0;
81 	seen_cr = seen_nul = 0;
82 	while ((c = pop_get_char()) >= 0) {
83 		if (c == '\n') {
84 			if (seen_cr) line[pos - 1] = 0;
85 			break;
86 		}
87 		if (pos < size - 1)
88 			seen_cr = ((line[pos++] = c) == '\r');
89 		else
90 			seen_cr = 0;
91 		seen_nul |= !c;
92 	}
93 	line[pos] = 0;
94 
95 	if (seen_nul)
96 		line[0] = 0;
97 
98 	if (pos || c >= 0)
99 		return line;
100 	else
101 		return NULL;
102 }
103 
pop_handle_state(struct pop_command * commands)104 int pop_handle_state(struct pop_command *commands)
105 {
106 	char line[POP_BUFFER_SIZE];
107 	char *params;
108 	struct pop_command *command;
109 	int response;
110 
111 	if (sigsetjmp(pop_timed_out, 1)) return POP_CRASH_NETTIME;
112 
113 	while (pop_get_line(line, sizeof(line))) {
114 		if ((params = strchr(line, ' '))) {
115 			*params++ = 0;
116 			if (!*params) params = NULL;
117 		}
118 
119 		response = POP_ERROR;
120 		for (command = commands; command->name; command++)
121 		if (!strcasecmp(command->name, line)) {
122 			response = command->handler(params);
123 			break;
124 		}
125 
126 		switch (response) {
127 		case POP_OK:
128 			if (pop_reply_ok()) return POP_CRASH_NETFAIL;
129 			break;
130 
131 		case POP_ERROR:
132 			if (pop_reply_error()) return POP_CRASH_NETFAIL;
133 
134 		case POP_QUIET:
135 			break;
136 
137 		case POP_LEAVE:
138 			if (pop_reply_ok()) return POP_CRASH_NETFAIL;
139 
140 		default:
141 			return response;
142 		}
143 	}
144 
145 	return POP_CRASH_NETFAIL;
146 }
147 
pop_get_param(char ** params)148 char *pop_get_param(char **params)
149 {
150 	char *current, *next;
151 
152 	if ((current = *params)) {
153 		if ((next = strchr(current, ' '))) {
154 			*next++ = 0;
155 			*params = *next ? next : NULL;
156 		} else
157 			*params = NULL;
158 
159 		if (strlen(current) > 40) current = NULL;
160 	}
161 
162 	return current;
163 }
164 
pop_get_int(char ** params)165 int pop_get_int(char **params)
166 {
167 	char *param, *error;
168 	long value;
169 
170 	if ((param = pop_get_param(params))) {
171 /* SUSv2 says:
172  * "Because 0, LONG_MIN and LONG_MAX are returned on error and are also
173  * valid returns on success, an application wishing to check for error
174  * situations should set errno to 0, then call strtol(), then check errno." */
175 		errno = 0;
176 		value = strtol(param, &error, 10);
177 		if (errno || !*param || *error ||
178 		    value < 0 || (long)(int)value != value)
179 			return -1;
180 
181 		return (int)value;
182 	}
183 
184 	return -1;
185 }
186 
pop_reply(char * format,...)187 int pop_reply(char *format, ...)
188 {
189 	va_list args;
190 
191 	va_start(args, format);
192 	vfprintf(stdout, format, args);
193 	va_end(args);
194 
195 	putc('\r', stdout);
196 	putc('\n', stdout);
197 
198 	switch (format[0]) {
199 	case '+':
200 	case '-':
201 		return fflush(stdout);
202 
203 	case '.':
204 		if (!format[1]) return fflush(stdout);
205 	}
206 
207 	return ferror(stdout);
208 }
209 
pop_reply_ok(void)210 int pop_reply_ok(void)
211 {
212 	return pop_reply("+OK");
213 }
214 
pop_reply_error(void)215 int pop_reply_error(void)
216 {
217 	return pop_reply("-ERR");
218 }
219 
pop_reply_multiline(int fd,unsigned long size,int lines)220 int pop_reply_multiline(int fd, unsigned long size, int lines)
221 {
222 	char *in_buffer, *out_buffer;
223 	char *in, *out;
224 	int in_block, out_block;
225 	int start, body;
226 
227 	if (lines >= 0) lines++;
228 
229 	if (pop_reply_ok()) return POP_CRASH_NETFAIL;
230 
231 	in_buffer = malloc(RETR_BUFFER_SIZE * 3);
232 	if (!in_buffer) return POP_CRASH_SERVER;
233 	out_buffer = &in_buffer[RETR_BUFFER_SIZE];
234 
235 	start = 1;
236 	body = 0;
237 	while (size && lines) {
238 		if (size > RETR_BUFFER_SIZE)
239 			in_block = read(fd, in_buffer, RETR_BUFFER_SIZE);
240 		else
241 			in_block = read(fd, in_buffer, size);
242 		if (in_block <= 0) {
243 			free(in_buffer);
244 			return POP_CRASH_SERVER;
245 		}
246 
247 		in = in_buffer;
248 		out = out_buffer;
249 		while (in < &in_buffer[in_block] && lines)
250 		switch (*in) {
251 		case '\n':
252 			*out++ = '\r';
253 			*out++ = *in++;
254 			if (start) body = 1;
255 			if (body) lines--;
256 			start = 1;
257 			break;
258 
259 		case '.':
260 			if (start) *out++ = '.';
261 
262 		default:
263 			*out++ = *in++;
264 			start = 0;
265 		}
266 
267 		out_block = out - out_buffer;
268 		if (write_loop(1, out_buffer, out_block) != out_block) {
269 			free(in_buffer);
270 			return POP_CRASH_NETFAIL;
271 		}
272 
273 		size -= in_block;
274 	}
275 
276 	free(in_buffer);
277 
278 	if (!start)
279 	if (pop_reply("%s", "")) return POP_CRASH_NETFAIL;
280 
281 	return POP_OK;
282 }
283 
pop_reply_terminate(void)284 int pop_reply_terminate(void)
285 {
286 	return pop_reply(".");
287 }
288