1 /* GNU Mailutils -- a suite of utilities for electronic mail
2    Copyright (C) 2004-2021 Free Software Foundation, Inc.
3 
4    This library is free software; you can redistribute it and/or
5    modify it under the terms of the GNU Lesser General Public
6    License as published by the Free Software Foundation; either
7    version 3 of the License, or (at your option) any later version.
8 
9    This library is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12    Lesser General Public License for more details.
13 
14    You should have received a copy of the GNU Lesser General
15    Public License along with this library.  If not, see
16    <http://www.gnu.org/licenses/>. */
17 
18 #ifdef HAVE_CONFIG_H
19 # include <config.h>
20 #endif
21 
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <unistd.h>
25 #include <fcntl.h>
26 #include <sys/types.h>
27 #include <sys/stat.h>
28 #include <string.h>
29 
30 #include <mailutils/daemon.h>
31 #include <mailutils/errno.h>
32 #include <mailutils/error.h>
33 #include <mailutils/nls.h>
34 
35 static char *pidfile;
36 static pid_t current_pid;
37 
38 /* Return 0 if DIR is writable for EUID/EGID.
39    Otherwise, return error code. */
40 static int
ewraccess(const char * dir)41 ewraccess (const char *dir)
42 {
43   struct stat st;
44   if (stat (dir, &st))
45     return errno;
46   if ((st.st_mode & S_IWOTH)
47       || (st.st_gid == getegid () && (st.st_mode & S_IWGRP))
48       || (st.st_uid == geteuid () && (st.st_mode & S_IWUSR)))
49     return 0;
50   else
51     return EACCES;
52 }
53 
54 /* Return 0 if DIR is writable. If necessary and possible, raise to
55    EUID 0, in that case return prior EUID in the memory location pointed to
56    by PUID. */
57 static int
access_dir(const char * dir,uid_t * puid)58 access_dir (const char *dir, uid_t *puid)
59 {
60   int ec = ewraccess (dir);
61   if (ec)
62     {
63       if (ec == EACCES && access (dir, W_OK) == 0)
64 	{
65 	  uid_t uid = geteuid ();
66 	  /* See if we can become root */
67 	  if (uid && getuid () == 0 && seteuid (0) == 0)
68 	    {
69 	      *puid = uid;
70 	      return 0;
71 	    }
72 	}
73     }
74   return ec;
75 }
76 
77 int
mu_daemon_create_pidfile(const char * filename)78 mu_daemon_create_pidfile (const char *filename)
79 {
80   char *p;
81   int fd;
82   uid_t uid = 0;
83   int rc;
84 
85   if (filename[0] != '/')
86     return EINVAL;
87 
88   if (pidfile)
89     free (pidfile);
90   pidfile = strdup (filename);
91   if (!pidfile)
92     return ENOMEM;
93 
94   /* Determine the hosting directory name */
95   p = strrchr (pidfile, '/');
96   if (pidfile == p)
97     {
98       free (pidfile);
99       pidfile = NULL;
100       /* Sorry, pidfiles in root dir are not allowed */
101       return EINVAL;
102     }
103   /* Check if we have write access to the directory */
104   *p = 0;
105   rc = access_dir (pidfile, &uid);
106   if (rc)
107     {
108       /* Nope, clean up and return */
109       free (pidfile);
110       pidfile = NULL;
111       return rc;
112     }
113 
114   /* Restore directory separator */
115   *p = '/';
116 
117   unlink (pidfile);
118   current_pid = getpid ();
119 
120   if ((fd = open (pidfile, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, 0644)) != -1)
121     {
122       FILE *fp = fdopen (fd, "w");
123       if (!fp)
124 	{
125 	  rc = errno;
126 	  free (pidfile);
127 	  close (fd);
128 	}
129       else
130 	{
131 	  fprintf (fp, "%lu", (unsigned long) current_pid);
132 	  fclose (fp);
133 	  atexit (mu_daemon_remove_pidfile);
134 	}
135     }
136   else
137     {
138       rc = errno;
139       free (pidfile);
140       pidfile = NULL;
141     }
142 
143   /* Restore previous EUID value. */
144   if (uid)
145     seteuid (uid);
146 
147   return rc;
148 }
149 
150 void
mu_daemon_remove_pidfile(void)151 mu_daemon_remove_pidfile (void)
152 {
153   if (getpid () == current_pid)
154     {
155       int rc;
156       uid_t uid = 0;
157 
158       /* Determine the hosting directory name */
159       char *p = strrchr (pidfile, '/');
160       if (pidfile == p)
161 	{
162 	  /* Should not happen */
163 	  abort ();
164 	}
165       /* Check if we have write access to the directory */
166       *p = 0;
167       rc = access_dir (pidfile, &uid);
168       *p = '/';
169       if (rc == 0)
170 	{
171 	  if (unlink (pidfile) && errno != ENOENT)
172 	    rc = errno;
173 	  else
174 	    rc = 0;
175 	}
176 
177       if (rc)
178 	mu_error (_("cannot remove pidfile %s: %s"),
179 		  pidfile, mu_strerror (rc));
180 
181       free (pidfile);
182       pidfile = NULL;
183     }
184 }
185 
186 
187 
188