peek_packet.c: always use at least 4K buffer
[girocco.git] / src / peek_packet.c
blob7f3dd738d53cd49a26cd19f257d0943002ce8a79
1 /*
3 peek_packet.c -- peek_packet utility to peek at incoming git-daemon request
4 Copyright (C) 2015,2020 Kyle J. McKay. All rights reserved.
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public License
8 as published by the Free Software Foundation; either version 2
9 of the License, or (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
23 This utility is intended to be used by a script front end to git daemon
24 running in inetd mode. The first thing the script does is call this utility
25 which attempts to peek the first incoming Git packet off the connection
26 and then output contents after the initial 4-character hex length upto but
27 excluding the first \0 character.
29 At that point the script can validate the incoming request and if it chooses
30 to allow it then exec git daemon to process it. Since the packet was peeked
31 it's still there to be read by git daemon.
33 Note that there is a hard-coded timeout of 30 seconds and a hard-coded limit
34 of PATH_MAX for the length of the initial packet.
36 On failure a non-zero exit code is returned. On success a 0 exit code is
37 returned and peeked text is output to stdout.
39 The connection to be peeked must be fd 0 and will have SO_KEEPALIVE set on it.
41 This utility does not take any arguments and ignores any that are given.
43 The output of a successful peek should be one of these:
45 git-upload-pack /<...>
46 git-upload-archive /<...>
47 git-receive-pack /<...>
49 where "<...>" is replaced with the repository path so, for example, doing
50 "git ls-remote git://example.com/foo.git" would result in this output:
52 git-upload-pack /foo.git
54 Note that the first character could be a ~ instead of a /, but it's
55 probably best to reject those.
58 #include <stdio.h>
59 #include <string.h>
60 #include <stdlib.h>
61 #include <unistd.h>
62 #include <limits.h>
63 #include <time.h>
64 #include <signal.h>
65 #include <sys/types.h>
66 #include <sys/socket.h>
68 /* Note that mod_reqtimeout has a default configuration of 20 seconds
69 * maximum to wait for the first byte of the initial request line and
70 * then no more than 40 seconds total, but after the first byte is
71 * received the rest must arrive at 500 bytes/sec or faster. That
72 * means 10000 bytes minimum in 40 seconds. We do not allow the
73 * initial Git packet to be longer than PATH_MAX (which is typically
74 * either 1024 or 4096). And since 20 + 1024/500 = 22.048 and
75 * 20 + 4096/500 = 28.192 using 30 seconds for a total timeout is
76 * quite reasonable in comparison to mod_reqtimeout's default conf.
79 #define TIMEOUT_SECS 30 /* no more than 30 seconds for initial packet */
81 #define POLL_QUANTUM 100000U /* how often to poll in microseconds */
83 #if !defined(PATH_MAX) || PATH_MAX+0 < 4096
84 #define BUFF_MAX 4096
85 #else
86 #define BUFF_MAX PATH_MAX
87 #endif
89 static int xdig(char c)
91 if ('0' <= c && c <= '9')
92 return c - '0';
93 if ('a' <= c && c <= 'f')
94 return c - 'a' + 10;
95 if ('A' <= c && c <= 'F')
96 return c - 'A' + 10;
97 return -1;
100 static char buffer[BUFF_MAX];
101 static time_t expiry;
103 /* Ideally we could just use MSG_PEEK + MSG_WAITALL, and that works nicely
104 * on BSD-type distros. Unfortunately very bad things happen on Linux with
105 * that combination -- a CPU core runs at 100% until all the data arrives.
106 * So instead we omit the MSG_WAITALL and poll every POLL_QUANTUM interval
107 * to see if we've satisfied the requested amount yet.
109 static int recv_peekall(int fd, void *buff, size_t len)
111 int ans;
112 while ((ans = recv(fd, buff, len, MSG_PEEK)) > 0 && (size_t)ans < len) {
113 if (time(NULL) > expiry)
114 exit(2);
115 usleep(POLL_QUANTUM);
117 return ans < 0 ? -1 : (int)len;
120 static void handle_sigalrm(int s)
122 (void)s;
123 _exit(2);
126 static void clear_alarm(void)
128 alarm(0);
131 int main(int argc, char *argv[])
133 int len;
134 int xvals[4];
135 char hexlen[4];
136 size_t pktlen;
137 const char *nullptr;
138 int optval;
140 (void)argc;
141 (void)argv;
143 /* Ideally calling recv with MSG_PEEK would never, ever hang. However
144 * even with MSG_PEEK, recv still waits for at least the first message
145 * to arrive on the socket (unless it's non-blocking). For this reason
146 * we set an alarm timer at TIMEOUT_SECS + 2 to make sure we don't
147 * remain stuck in the recv call waiting for the first message.
149 signal(SIGALRM, handle_sigalrm);
150 alarm(TIMEOUT_SECS + 2); /* Some slop as this shouldn't be needed */
151 atexit(clear_alarm); /* Probably not necessary, but do it anyway */
153 expiry = time(NULL) + TIMEOUT_SECS;
155 optval = 1;
156 if (setsockopt(0, SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof(optval)))
157 return 1;
159 len = recv_peekall(0, hexlen, 4);
160 if (len != 4)
161 return 1;
163 if ((xvals[0]=xdig(hexlen[0])) < 0 ||
164 (xvals[1]=xdig(hexlen[1])) < 0 ||
165 (xvals[2]=xdig(hexlen[2])) < 0 ||
166 (xvals[3]=xdig(hexlen[3])) < 0)
167 return 1;
168 pktlen = ((unsigned)xvals[0] << 12) |
169 ((unsigned)xvals[1] << 8) |
170 ((unsigned)xvals[2] << 4) |
171 (unsigned)xvals[3];
172 if (pktlen < 22 || pktlen > sizeof(buffer))
173 return 1;
175 len = recv_peekall(0, buffer, pktlen);
176 if (len != (int)pktlen)
177 return 1;
179 if (memcmp(buffer+4, "git-", 4)) /* sanity check */
180 return 1;
181 nullptr = (const char *)memchr(buffer+4, 0, pktlen-4);
182 if (!nullptr || nullptr < (buffer+21))
183 return 1;
184 puts(buffer + 4);
186 return 0;