xref: /openbsd-src/usr.bin/cu/xmodem.c (revision 50b7afb2c2c0993b0894d4e34bf857cb13ed9c80)
1 /* $OpenBSD: xmodem.c,v 1.6 2013/11/12 13:54:51 deraadt Exp $ */
2 
3 /*
4  * Copyright (c) 2012 Nicholas Marriott <nicm@openbsd.org>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
15  * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
16  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <sys/types.h>
20 
21 #include <errno.h>
22 #include <signal.h>
23 #include <stdio.h>
24 #include <string.h>
25 #include <termios.h>
26 #include <unistd.h>
27 
28 #include "cu.h"
29 
30 #define XMODEM_BLOCK 128
31 #define XMODEM_RETRIES 10
32 
33 #define XMODEM_SOH '\001'
34 #define XMODEM_EOT '\004'
35 #define XMODEM_ACK '\006'
36 #define XMODEM_NAK '\025'
37 #define XMODEM_SUB '\032'
38 #define XMODEM_C   '\103'
39 
40 volatile sig_atomic_t xmodem_stop;
41 
42 void		xmodem_signal(int sig);
43 uint16_t	xmodem_crc16(const u_char *buf, size_t len);
44 int		xmodem_read(char *c);
45 int		xmodem_write(const u_char *buf, size_t len);
46 
47 void
48 xmodem_signal(int sig)
49 {
50 	xmodem_stop = 1;
51 }
52 
53 uint16_t
54 xmodem_crc16(const u_char *buf, size_t len)
55 {
56 	uint16_t	crc;
57 	u_int		i, j;
58 
59 	crc = 0;
60 	for (i = 0; i < len; i++) {
61 		crc = crc ^ *buf++ << 8;
62 		for (j = 0; j < 8; j++)
63 			if (crc & 0x8000)
64 				crc = crc << 1 ^ 0x1021;
65 			else
66 				crc = crc << 1;
67 	}
68 	return (crc);
69 }
70 
71 int
72 xmodem_read(char *c)
73 {
74 	for (;;) {
75 		switch (read(line_fd, c, 1)) {
76 		case -1:
77 			if (errno == EINTR && !xmodem_stop)
78 				continue;
79 			return (-1);
80 		case 0:
81 			errno = EPIPE;
82 			return (-1);
83 		case 1:
84 			return (0);
85 		}
86 	}
87 }
88 
89 int
90 xmodem_write(const u_char *buf, size_t len)
91 {
92 	ssize_t	n;
93 
94 	while (len > 0) {
95 		n = write(line_fd, buf, len);
96 		if (n == -1) {
97 			if (errno == EINTR && !xmodem_stop)
98 				continue;
99 			return (-1);
100 		}
101 		buf += n;
102 		len -= n;
103 	}
104 	return (0);
105 }
106 
107 void
108 xmodem_send(const char *file)
109 {
110 	FILE			*f;
111 	u_char			 buf[3 + XMODEM_BLOCK + 2], c;
112 	size_t			 len, pktlen;
113 	uint8_t			 num;
114 	uint16_t		 crc;
115 	int			 crc_mode;
116 	u_int			 i, total;
117 	struct termios		 tio;
118 	struct sigaction	 act, oact;
119 
120 	f = fopen(file, "r");
121 	if (f == NULL) {
122 		cu_warn("%s", file);
123 		return;
124 	}
125 
126 	memset(&act, 0, sizeof(act));
127 	sigemptyset(&act.sa_mask);
128 	act.sa_flags = 0;
129 	act.sa_handler = xmodem_signal;
130 	if (sigaction(SIGINT, &act, &oact) != 0)
131 		cu_err(1, "sigaction");
132 	xmodem_stop = 0;
133 
134 	if (isatty(STDIN_FILENO)) {
135 		memcpy(&tio, &saved_tio, sizeof(tio));
136 		tio.c_lflag &= ~ECHO;
137 		if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &tio) != 0)
138 			cu_err(1, "tcsetattr");
139 	}
140 
141 	tcflush(line_fd, TCIFLUSH);
142 	if (xmodem_read(&c) != 0)
143 		goto fail;
144 	if (c == XMODEM_C)
145 		crc_mode = 1;
146 	else if (c == XMODEM_NAK)
147 		crc_mode = 0;
148 	else {
149 		cu_warnx("%s: unexpected response \%03hho", file, c);
150 		goto fail;
151 	}
152 
153 	num = 1;
154 	total = 1;
155 	pktlen = 3 + XMODEM_BLOCK + (crc_mode ? 2 : 1);
156 	for (;;) {
157 		len = fread(buf + 3, 1, XMODEM_BLOCK, f);
158 		if (len == 0)
159 			break;
160 		memset(buf + 3 + len, XMODEM_SUB, XMODEM_BLOCK - len);
161 
162 		buf[0] = XMODEM_SOH;
163 		buf[1] = num;
164 		buf[2] = 255 - num;
165 
166 		if (crc_mode) {
167 			crc = xmodem_crc16(buf + 3, XMODEM_BLOCK);
168 			buf[3 + XMODEM_BLOCK] = crc >> 8;
169 			buf[3 + XMODEM_BLOCK + 1] = crc & 0xFF;
170 		} else {
171 			buf[3 + XMODEM_BLOCK] = 0;
172 			for (i = 0; i < XMODEM_BLOCK; i++)
173 				buf[3 + XMODEM_BLOCK] += buf[3 + i];
174 		}
175 
176 		for (i = 0; i < XMODEM_RETRIES; i++) {
177 			if (xmodem_stop) {
178 				errno = EINTR;
179 				goto fail;
180 			}
181 			cu_warnx("%s: sending block %u (attempt %u)", file,
182 			    total, 1 + i);
183 			if (xmodem_write(buf, pktlen) != 0)
184 				goto fail;
185 
186 			if (xmodem_read(&c) != 0)
187 				goto fail;
188 			if (c == XMODEM_ACK)
189 				break;
190 			if (c != XMODEM_NAK) {
191 				cu_warnx("%s: unexpected response \%03hho",
192 				    file, c);
193 			}
194 		}
195 		if (i == XMODEM_RETRIES) {
196 			cu_warnx("%s: too many retries", file);
197 			goto out;
198 		}
199 
200 		if (len < XMODEM_BLOCK)
201 			break;
202 		num++;
203 		total++;
204 	}
205 
206 	buf[0] = XMODEM_EOT;
207 	if (xmodem_write(buf, 1) != 0)
208 		goto fail;
209 	cu_warnx("%s: completed %u blocks", file, num);
210 
211 	goto out;
212 
213 fail:
214 	cu_warn("%s", file);
215 
216 out:
217 	set_termios();
218 
219 	sigaction(SIGINT, &oact, NULL);
220 
221 	fclose(f);
222 }
223