Fix FSF address (Tobias Mueller, #470445)
[platform/upstream/evolution-data-server.git] / camel / camel-lock-helper.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2  *
3  * Copyright (C) 2001 Ximian Inc.
4  *
5  * Authors: Michael Zucchi <notzed@ximian.com>
6  *
7  * This program is free software; you can redistribute it and/or 
8  * modify it under the terms of version 2 of the GNU Lesser General Public 
9  * License as published by the Free Software Foundation.
10  *
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 Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser 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
19  * USA
20  */
21
22 /* lock helper process */
23
24 #include <errno.h>
25 #include <fcntl.h>
26 #include <signal.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <time.h>
31 #include <unistd.h>
32 #include <utime.h>
33 #include <sys/stat.h>
34 #include <sys/time.h>
35 #include <sys/types.h>
36
37 #define SETEUID_SAVES (1)
38
39 /* we try and include as little as possible */
40
41 #include "camel-lock-helper.h"
42 #include "camel-lock.h"
43
44 #define d(x)
45
46 /* keeps track of open locks */
47 struct _lock_info {
48         struct _lock_info *next;
49         uid_t uid;
50         int id;
51         int depth;
52         time_t stamp;           /* when last updated */
53         char path[1];
54 };
55
56 static int lock_id = 0;
57 static struct _lock_info *lock_info_list;
58 static uid_t lock_root_uid = -1;
59 static uid_t lock_real_uid = -1;
60
61 /* utility functions */
62
63 static int read_n(int fd, void *buffer, int inlen)
64 {
65         char *p = buffer;
66         int len, left = inlen;
67
68         do {
69                 len = read(fd, p, left);
70                 if (len == -1) {
71                         if (errno != EINTR)
72                                 return -1;
73                 } else {
74                         left -= len;
75                         p += len;
76                 }
77         } while (left > 0 && len != 0);
78
79         return inlen - left;
80 }
81
82 static int write_n(int fd, void *buffer, int inlen)
83 {
84         char *p = buffer;
85         int len, left = inlen;
86
87         do {
88                 len = write(fd, p, left);
89                 if (len == -1) {
90                         if (errno != EINTR)
91                                 return -1;
92                 } else {
93                         left -= len;
94                         p += len;
95                 }
96         } while (left > 0);
97
98         return inlen;
99 }
100
101 void
102 camel_exception_setv (CamelException *ex, ExceptionId id, const char *format, ...)
103 {
104         ;
105 }
106
107 void
108 camel_exception_clear (CamelException *exception)
109 {
110         ;
111 }
112
113 char *gettext (const char *msgid);
114
115 char *
116 gettext (const char *msgid)
117 {
118         return NULL;
119 }
120
121 static int lock_path(const char *path, guint32 *lockid)
122 {
123         struct _lock_info *info = NULL;
124         int res = CAMEL_LOCK_HELPER_STATUS_OK;
125         struct stat st;
126
127         d(fprintf(stderr, "locking path '%s' id = %d\n", path, lock_id));
128
129         /* check to see if we have it locked already, make the lock 'recursive' */
130         /* we could also error i suppose, but why bother */
131         info = lock_info_list;
132         while (info) {
133                 if (!strcmp(info->path, path)) {
134                         info->depth++;
135                         return CAMEL_LOCK_HELPER_STATUS_OK;
136                 }
137                 info = info->next;
138         }
139
140         /* check we are allowed to lock it, we must own it, be able to write to it, and it has to exist */
141         if (stat(path, &st) == -1
142             || st.st_uid != getuid()
143             || !S_ISREG(st.st_mode)
144             || (st.st_mode & 0400) == 0) {
145                 return CAMEL_LOCK_HELPER_STATUS_INVALID;
146         }
147
148         info = malloc(sizeof(*info) + strlen(path));
149         if (info == NULL) {
150                 res = CAMEL_LOCK_HELPER_STATUS_NOMEM;
151                 goto fail;
152         }
153
154         /* we try the real uid first, and if that fails, try the 'root id' */
155         if (camel_lock_dot(path, NULL) == -1) {
156 #ifdef SETEUID_SAVES
157                 if (lock_real_uid != lock_root_uid) {
158                         if (seteuid(lock_root_uid) != -1) {
159                                 if (camel_lock_dot(path, NULL) == -1) {
160                                         seteuid(lock_real_uid);
161                                         res = CAMEL_LOCK_HELPER_STATUS_SYSTEM;
162                                         goto fail;
163                                 }
164                                 seteuid(lock_real_uid);
165                         } else {
166                                 res = CAMEL_LOCK_HELPER_STATUS_SYSTEM;
167                                 goto fail;
168                         }
169                 } else {
170                         res = CAMEL_LOCK_HELPER_STATUS_SYSTEM;
171                         goto fail;
172                 }
173 #else
174                 res = CAMEL_LOCK_HELPER_STATUS_SYSTEM;
175                 goto fail;
176 #endif
177         } else {
178                 info->uid = lock_real_uid;
179         }
180
181         strcpy(info->path, path);
182         info->id = lock_id;
183         info->depth = 1;
184         info->next = lock_info_list;
185         info->stamp = time (NULL);
186         lock_info_list = info;
187
188         if (lockid)
189                 *lockid = lock_id;
190
191         lock_id++;
192
193         d(fprintf(stderr, "lock ok\n"));
194
195         return res;
196 fail:
197         d(fprintf(stderr, "lock failed\n"));
198
199         if (info)
200                 free(info);
201
202         return res;
203 }
204
205 static int unlock_id(guint32 lockid)
206 {
207         struct _lock_info *info, *p;
208
209         d(fprintf(stderr, "unlocking id '%d'\n", lockid));
210
211         p = (struct _lock_info *)&lock_info_list;
212         info = p->next;
213         while (info) {
214                 if (info->id == lockid) {
215                         d(fprintf(stderr, "found id %d path '%s'\n", lockid, info->path));
216                         info->depth--;
217                         if (info->depth <= 0) {
218 #ifdef SETEUID_SAVES
219                                 if (info->uid != lock_real_uid) {
220                                         seteuid(lock_root_uid);
221                                         camel_unlock_dot(info->path);
222                                         seteuid(lock_real_uid);
223                                 } else
224 #endif
225                                         camel_unlock_dot(info->path);
226
227                                 p->next = info->next;
228                                 free(info);
229                         }
230
231                         return CAMEL_LOCK_HELPER_STATUS_OK;
232                 }
233                 p = info;
234                 info = info->next;
235         }
236
237         d(fprintf(stderr, "unknown id asked to be unlocked %d\n", lockid));
238         return CAMEL_LOCK_HELPER_STATUS_PROTOCOL;
239 }
240
241 static void lock_touch(const char *path)
242 {
243         char *name;
244
245         /* we could also check that we haven't had our lock stolen from us here */
246
247         name = alloca(strlen(path) + 10);
248         sprintf(name, "%s.lock", path);
249
250         d(fprintf(stderr, "Updating lock %s\n", name));
251         utime(name, NULL);
252 }
253
254 static void setup_process(void)
255 {
256         struct sigaction sa;
257         sigset_t sigset;
258         
259         /* ignore sigint/sigio */
260         sa.sa_handler = SIG_IGN;
261         sigemptyset (&sa.sa_mask);
262         sa.sa_flags = 0;
263         
264         sigemptyset(&sigset);
265         sigaddset(&sigset, SIGIO);
266         sigaddset(&sigset, SIGINT);
267         sigprocmask(SIG_UNBLOCK, &sigset, NULL);
268         
269         sigaction (SIGIO, &sa, NULL);
270         sigaction (SIGINT, &sa, NULL);
271         
272         /* FIXME: add more sanity checks/setup here */
273
274 #ifdef SETEUID_SAVES
275         /* here we change to the real user id, this is probably not particularly
276            portable so may need configure checks */
277         lock_real_uid = getuid();
278         lock_root_uid = geteuid();
279         if (lock_real_uid != lock_root_uid)
280                 seteuid(lock_real_uid);
281 #endif
282 }
283
284 int main(int argc, char **argv)
285 {
286         struct _CamelLockHelperMsg msg;
287         int len;
288         int res;
289         char *path;
290         fd_set rset;
291         struct timeval tv;
292         struct _lock_info *info;
293
294         setup_process();
295
296         do {
297                 /* do a poll/etc, so we can refresh the .locks as required ... */
298                 FD_ZERO(&rset);
299                 FD_SET(STDIN_FILENO, &rset);
300
301                 /* check the minimum timeout we need to refresh the next oldest lock */
302                 if (lock_info_list) {
303                         time_t now = time (NULL);
304                         time_t left;
305                         time_t delay = CAMEL_DOT_LOCK_REFRESH;
306
307                         info = lock_info_list;
308                         while (info) {
309                                 left = CAMEL_DOT_LOCK_REFRESH - (now - info->stamp);
310                                 left = MAX(left, 0);
311                                 delay = MIN(left, delay);
312                                 info = info->next;
313                         }
314
315                         tv.tv_sec = delay;
316                         tv.tv_usec = 0;
317                 }
318                 
319                 d(fprintf(stderr, "lock helper waiting for input\n"));
320                 if (select(STDIN_FILENO+1, &rset, NULL, NULL, lock_info_list?&tv:NULL) == -1) {
321                         if (errno == EINTR)
322                                 break;
323
324                         continue;
325                 }
326
327                 /* did we get a timeout?  scan for any locks that need updating */
328                 if (!FD_ISSET(STDIN_FILENO, &rset)) {
329                         time_t now = time (NULL);
330                         time_t left;
331
332                         d(fprintf(stderr, "Got a timeout, checking locks\n"));
333
334                         info = lock_info_list;
335                         while (info) {
336                                 left = (now - info->stamp);
337                                 if (left >= CAMEL_DOT_LOCK_REFRESH) {
338                                         lock_touch(info->path);
339                                         info->stamp = now;
340                                 }
341                                 info = info->next;
342                         }
343                         
344                         continue;
345                 }
346
347
348                 len = read_n(STDIN_FILENO, &msg, sizeof(msg));
349                 if (len == 0)
350                         break;
351
352                 res = CAMEL_LOCK_HELPER_STATUS_PROTOCOL;
353                 if (len == sizeof(msg) && msg.magic == CAMEL_LOCK_HELPER_MAGIC) {
354                         switch(msg.id) {
355                         case CAMEL_LOCK_HELPER_LOCK:
356                                 res = CAMEL_LOCK_HELPER_STATUS_NOMEM;
357                                 if (msg.data > 0xffff) {
358                                         res = CAMEL_LOCK_HELPER_STATUS_PROTOCOL;
359                                 } else if ((path = malloc(msg.data+1)) != NULL) {
360                                         res = CAMEL_LOCK_HELPER_STATUS_PROTOCOL;
361                                         len = read_n(STDIN_FILENO, path, msg.data);
362                                         if (len == msg.data) {
363                                                 path[len] = 0;
364                                                 res = lock_path(path, &msg.data);
365                                         }
366                                         free(path);
367                                 }
368                                 break;
369                         case CAMEL_LOCK_HELPER_UNLOCK:
370                                 res = unlock_id(msg.data);
371                                 break;
372                         }
373                 }
374                 d(fprintf(stderr, "returning result %d\n", res));
375                 msg.id = res;
376                 msg.magic = CAMEL_LOCK_HELPER_RETURN_MAGIC;
377                 write_n(STDOUT_FILENO, &msg, sizeof(msg));
378         } while (1);
379
380         d(fprintf(stderr, "parent exited, clsoing down remaining id's\n"));
381         while (lock_info_list)
382                 unlock_id(lock_info_list->id);
383
384         return 0;
385 }