xref: /src/libexec/pkg-serve/pkg-serve.c (revision b42e852e89cb04cceb6e0226d6a08cab13fb6e90)
1*b42e852eSBaptiste Daroussin /*-
2*b42e852eSBaptiste Daroussin  * SPDX-License-Identifier: BSD-2-Clause
3*b42e852eSBaptiste Daroussin  *
4*b42e852eSBaptiste Daroussin  * Copyright (c) 2026 Baptiste Daroussin <bapt@FreeBSD.org>
5*b42e852eSBaptiste Daroussin  */
6*b42e852eSBaptiste Daroussin 
7*b42e852eSBaptiste Daroussin /*
8*b42e852eSBaptiste Daroussin  * Speaks the same protocol as "pkg ssh" (see pkg-ssh(8)):
9*b42e852eSBaptiste Daroussin  *   -> ok: pkg-serve <version>
10*b42e852eSBaptiste Daroussin  *   <- get <file> <mtime>
11*b42e852eSBaptiste Daroussin  *   -> ok: <size>\n<data>   or   ok: 0\n   or   ko: <error>\n
12*b42e852eSBaptiste Daroussin  *   <- quit
13*b42e852eSBaptiste Daroussin  */
14*b42e852eSBaptiste Daroussin 
15*b42e852eSBaptiste Daroussin #include <sys/capsicum.h>
16*b42e852eSBaptiste Daroussin #include <sys/stat.h>
17*b42e852eSBaptiste Daroussin 
18*b42e852eSBaptiste Daroussin #include <ctype.h>
19*b42e852eSBaptiste Daroussin #include <err.h>
20*b42e852eSBaptiste Daroussin #include <errno.h>
21*b42e852eSBaptiste Daroussin #include <fcntl.h>
22*b42e852eSBaptiste Daroussin #include <inttypes.h>
23*b42e852eSBaptiste Daroussin #include <stdio.h>
24*b42e852eSBaptiste Daroussin #include <stdlib.h>
25*b42e852eSBaptiste Daroussin #include <string.h>
26*b42e852eSBaptiste Daroussin #include <unistd.h>
27*b42e852eSBaptiste Daroussin 
28*b42e852eSBaptiste Daroussin #define VERSION "0.1"
29*b42e852eSBaptiste Daroussin #define BUFSZ 32768
30*b42e852eSBaptiste Daroussin 
31*b42e852eSBaptiste Daroussin static void
usage(void)32*b42e852eSBaptiste Daroussin usage(void)
33*b42e852eSBaptiste Daroussin {
34*b42e852eSBaptiste Daroussin 	fprintf(stderr, "usage: pkg-serve basedir\n");
35*b42e852eSBaptiste Daroussin 	exit(EXIT_FAILURE);
36*b42e852eSBaptiste Daroussin }
37*b42e852eSBaptiste Daroussin 
38*b42e852eSBaptiste Daroussin int
main(int argc,char * argv[])39*b42e852eSBaptiste Daroussin main(int argc, char *argv[])
40*b42e852eSBaptiste Daroussin {
41*b42e852eSBaptiste Daroussin 	struct stat st;
42*b42e852eSBaptiste Daroussin 	cap_rights_t rights;
43*b42e852eSBaptiste Daroussin 	char *line = NULL;
44*b42e852eSBaptiste Daroussin 	char *file, *age;
45*b42e852eSBaptiste Daroussin 	size_t linecap = 0, r, toread;
46*b42e852eSBaptiste Daroussin 	ssize_t linelen;
47*b42e852eSBaptiste Daroussin 	off_t remaining;
48*b42e852eSBaptiste Daroussin 	time_t mtime;
49*b42e852eSBaptiste Daroussin 	char *end;
50*b42e852eSBaptiste Daroussin 	int fd, ffd;
51*b42e852eSBaptiste Daroussin 	char buf[BUFSZ];
52*b42e852eSBaptiste Daroussin 	const char *basedir;
53*b42e852eSBaptiste Daroussin 
54*b42e852eSBaptiste Daroussin 	if (argc != 2)
55*b42e852eSBaptiste Daroussin 		usage();
56*b42e852eSBaptiste Daroussin 
57*b42e852eSBaptiste Daroussin 	basedir = argv[1];
58*b42e852eSBaptiste Daroussin 
59*b42e852eSBaptiste Daroussin 	if ((fd = open(basedir, O_DIRECTORY | O_RDONLY | O_CLOEXEC)) < 0)
60*b42e852eSBaptiste Daroussin 		err(EXIT_FAILURE, "open(%s)", basedir);
61*b42e852eSBaptiste Daroussin 
62*b42e852eSBaptiste Daroussin 	cap_rights_init(&rights, CAP_READ, CAP_FSTATAT, CAP_LOOKUP,
63*b42e852eSBaptiste Daroussin 	    CAP_FCNTL);
64*b42e852eSBaptiste Daroussin 	if (cap_rights_limit(fd, &rights) < 0 && errno != ENOSYS)
65*b42e852eSBaptiste Daroussin 		err(EXIT_FAILURE, "cap_rights_limit");
66*b42e852eSBaptiste Daroussin 
67*b42e852eSBaptiste Daroussin 	if (cap_enter() < 0 && errno != ENOSYS)
68*b42e852eSBaptiste Daroussin 		err(EXIT_FAILURE, "cap_enter");
69*b42e852eSBaptiste Daroussin 
70*b42e852eSBaptiste Daroussin 	printf("ok: pkg-serve " VERSION "\n");
71*b42e852eSBaptiste Daroussin 	fflush(stdout);
72*b42e852eSBaptiste Daroussin 
73*b42e852eSBaptiste Daroussin 	while ((linelen = getline(&line, &linecap, stdin)) > 0) {
74*b42e852eSBaptiste Daroussin 		/* trim newline */
75*b42e852eSBaptiste Daroussin 		if (linelen > 0 && line[linelen - 1] == '\n')
76*b42e852eSBaptiste Daroussin 			line[--linelen] = '\0';
77*b42e852eSBaptiste Daroussin 
78*b42e852eSBaptiste Daroussin 		if (linelen == 0)
79*b42e852eSBaptiste Daroussin 			continue;
80*b42e852eSBaptiste Daroussin 
81*b42e852eSBaptiste Daroussin 		if (strcmp(line, "quit") == 0)
82*b42e852eSBaptiste Daroussin 			break;
83*b42e852eSBaptiste Daroussin 
84*b42e852eSBaptiste Daroussin 		if (strncmp(line, "get ", 4) != 0) {
85*b42e852eSBaptiste Daroussin 			printf("ko: unknown command '%s'\n", line);
86*b42e852eSBaptiste Daroussin 			fflush(stdout);
87*b42e852eSBaptiste Daroussin 			continue;
88*b42e852eSBaptiste Daroussin 		}
89*b42e852eSBaptiste Daroussin 
90*b42e852eSBaptiste Daroussin 		file = line + 4;
91*b42e852eSBaptiste Daroussin 
92*b42e852eSBaptiste Daroussin 		if (*file == '\0') {
93*b42e852eSBaptiste Daroussin 			printf("ko: bad command get, expecting 'get file age'\n");
94*b42e852eSBaptiste Daroussin 			fflush(stdout);
95*b42e852eSBaptiste Daroussin 			continue;
96*b42e852eSBaptiste Daroussin 		}
97*b42e852eSBaptiste Daroussin 
98*b42e852eSBaptiste Daroussin 		/* skip leading slash */
99*b42e852eSBaptiste Daroussin 		if (*file == '/')
100*b42e852eSBaptiste Daroussin 			file++;
101*b42e852eSBaptiste Daroussin 
102*b42e852eSBaptiste Daroussin 		/* find the age argument */
103*b42e852eSBaptiste Daroussin 		age = file;
104*b42e852eSBaptiste Daroussin 		while (*age != '\0' && !isspace((unsigned char)*age))
105*b42e852eSBaptiste Daroussin 			age++;
106*b42e852eSBaptiste Daroussin 
107*b42e852eSBaptiste Daroussin 		if (*age == '\0') {
108*b42e852eSBaptiste Daroussin 			printf("ko: bad command get, expecting 'get file age'\n");
109*b42e852eSBaptiste Daroussin 			fflush(stdout);
110*b42e852eSBaptiste Daroussin 			continue;
111*b42e852eSBaptiste Daroussin 		}
112*b42e852eSBaptiste Daroussin 
113*b42e852eSBaptiste Daroussin 		*age++ = '\0';
114*b42e852eSBaptiste Daroussin 
115*b42e852eSBaptiste Daroussin 		/* skip whitespace */
116*b42e852eSBaptiste Daroussin 		while (isspace((unsigned char)*age))
117*b42e852eSBaptiste Daroussin 			age++;
118*b42e852eSBaptiste Daroussin 
119*b42e852eSBaptiste Daroussin 		if (*age == '\0') {
120*b42e852eSBaptiste Daroussin 			printf("ko: bad command get, expecting 'get file age'\n");
121*b42e852eSBaptiste Daroussin 			fflush(stdout);
122*b42e852eSBaptiste Daroussin 			continue;
123*b42e852eSBaptiste Daroussin 		}
124*b42e852eSBaptiste Daroussin 
125*b42e852eSBaptiste Daroussin 		errno = 0;
126*b42e852eSBaptiste Daroussin 		mtime = (time_t)strtoimax(age, &end, 10);
127*b42e852eSBaptiste Daroussin 		if (errno != 0 || *end != '\0' || end == age) {
128*b42e852eSBaptiste Daroussin 			printf("ko: bad number %s\n", age);
129*b42e852eSBaptiste Daroussin 			fflush(stdout);
130*b42e852eSBaptiste Daroussin 			continue;
131*b42e852eSBaptiste Daroussin 		}
132*b42e852eSBaptiste Daroussin 
133*b42e852eSBaptiste Daroussin 		if (fstatat(fd, file, &st, AT_RESOLVE_BENEATH) == -1) {
134*b42e852eSBaptiste Daroussin 			printf("ko: file not found\n");
135*b42e852eSBaptiste Daroussin 			fflush(stdout);
136*b42e852eSBaptiste Daroussin 			continue;
137*b42e852eSBaptiste Daroussin 		}
138*b42e852eSBaptiste Daroussin 
139*b42e852eSBaptiste Daroussin 		if (!S_ISREG(st.st_mode)) {
140*b42e852eSBaptiste Daroussin 			printf("ko: not a file\n");
141*b42e852eSBaptiste Daroussin 			fflush(stdout);
142*b42e852eSBaptiste Daroussin 			continue;
143*b42e852eSBaptiste Daroussin 		}
144*b42e852eSBaptiste Daroussin 
145*b42e852eSBaptiste Daroussin 		if (st.st_mtime <= mtime) {
146*b42e852eSBaptiste Daroussin 			printf("ok: 0\n");
147*b42e852eSBaptiste Daroussin 			fflush(stdout);
148*b42e852eSBaptiste Daroussin 			continue;
149*b42e852eSBaptiste Daroussin 		}
150*b42e852eSBaptiste Daroussin 
151*b42e852eSBaptiste Daroussin 		if ((ffd = openat(fd, file, O_RDONLY | O_RESOLVE_BENEATH)) == -1) {
152*b42e852eSBaptiste Daroussin 			printf("ko: file not found\n");
153*b42e852eSBaptiste Daroussin 			fflush(stdout);
154*b42e852eSBaptiste Daroussin 			continue;
155*b42e852eSBaptiste Daroussin 		}
156*b42e852eSBaptiste Daroussin 
157*b42e852eSBaptiste Daroussin 		printf("ok: %" PRIdMAX "\n", (intmax_t)st.st_size);
158*b42e852eSBaptiste Daroussin 		fflush(stdout);
159*b42e852eSBaptiste Daroussin 
160*b42e852eSBaptiste Daroussin 		remaining = st.st_size;
161*b42e852eSBaptiste Daroussin 		while (remaining > 0) {
162*b42e852eSBaptiste Daroussin 			toread = sizeof(buf);
163*b42e852eSBaptiste Daroussin 			if ((off_t)toread > remaining)
164*b42e852eSBaptiste Daroussin 				toread = (size_t)remaining;
165*b42e852eSBaptiste Daroussin 			r = read(ffd, buf, toread);
166*b42e852eSBaptiste Daroussin 			if (r <= 0)
167*b42e852eSBaptiste Daroussin 				break;
168*b42e852eSBaptiste Daroussin 			if (fwrite(buf, 1, r, stdout) != r)
169*b42e852eSBaptiste Daroussin 				break;
170*b42e852eSBaptiste Daroussin 			remaining -= r;
171*b42e852eSBaptiste Daroussin 		}
172*b42e852eSBaptiste Daroussin 		close(ffd);
173*b42e852eSBaptiste Daroussin 		if (remaining > 0)
174*b42e852eSBaptiste Daroussin 			errx(EXIT_FAILURE, "%s: file truncated during transfer",
175*b42e852eSBaptiste Daroussin 			    file);
176*b42e852eSBaptiste Daroussin 		fflush(stdout);
177*b42e852eSBaptiste Daroussin 	}
178*b42e852eSBaptiste Daroussin 
179*b42e852eSBaptiste Daroussin 	return (EXIT_SUCCESS);
180*b42e852eSBaptiste Daroussin }
181