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.
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
86 #define BUFF_MAX PATH_MAX
89 static int xdig(char c
)
91 if ('0' <= c
&& c
<= '9')
93 if ('a' <= c
&& c
<= 'f')
95 if ('A' <= c
&& c
<= 'F')
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
)
112 while ((ans
= recv(fd
, buff
, len
, MSG_PEEK
)) > 0 && (size_t)ans
< len
) {
113 if (time(NULL
) > expiry
)
115 usleep(POLL_QUANTUM
);
117 return ans
< 0 ? -1 : (int)len
;
120 static void handle_sigalrm(int s
)
126 static void clear_alarm(void)
131 int main(int argc
, char *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
;
156 if (setsockopt(0, SOL_SOCKET
, SO_KEEPALIVE
, &optval
, sizeof(optval
)))
159 len
= recv_peekall(0, hexlen
, 4);
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)
168 pktlen
= ((unsigned)xvals
[0] << 12) |
169 ((unsigned)xvals
[1] << 8) |
170 ((unsigned)xvals
[2] << 4) |
172 if (pktlen
< 22 || pktlen
> sizeof(buffer
))
175 len
= recv_peekall(0, buffer
, pktlen
);
176 if (len
!= (int)pktlen
)
179 if (memcmp(buffer
+4, "git-", 4)) /* sanity check */
181 nullptr = (const char *)memchr(buffer
+4, 0, pktlen
-4);
182 if (!nullptr || nullptr < (buffer
+21))