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