1 /* GNU Mailutils -- a suite of utilities for electronic mail
2    Copyright (C) 1999-2021 Free Software Foundation, Inc.
3 
4    GNU Mailutils is free software; you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation; either version 3, or (at your option)
7    any later version.
8 
9    GNU Mailutils 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
12    GNU General Public License for more details.
13 
14    You should have received a copy of the GNU General Public License
15    along with GNU Mailutils.  If not, see <http://www.gnu.org/licenses/>. */
16 
17 #include "imap4d.h"
18 
19 int
make_interdir(const char * name,int delim,int perms)20 make_interdir (const char *name, int delim, int perms)
21 {
22   int rc;
23   size_t i;
24   struct mu_wordsplit ws;
25   char *namebuf;
26   size_t namelen = 0;
27   char delimbuf[2];
28 
29   namebuf = mu_alloc (strlen (name) + 1);
30   if (name[0] == '/')
31     namebuf[namelen++] = name[0];
32 
33   delimbuf[0] = delim;
34   delimbuf[1] = 0;
35   ws.ws_delim = delimbuf;
36   if (mu_wordsplit (name, &ws,
37 		    MU_WRDSF_DELIM|MU_WRDSF_SQUEEZE_DELIMS|
38 		    MU_WRDSF_NOVAR|MU_WRDSF_NOCMD))
39     {
40       mu_error (_("cannot split line `%s': %s"), name,
41 		mu_wordsplit_strerror (&ws));
42       free (namebuf);
43       return 1;
44     }
45 
46   rc = 0;
47   for (i = 0; rc == 0 && i < ws.ws_wordc - 1; i++)
48     {
49       struct stat st;
50 
51       strcpy (namebuf + namelen, ws.ws_wordv[i]);
52       namelen += strlen (ws.ws_wordv[i]);
53 
54       if (stat (namebuf, &st))
55 	{
56 	  if (errno == ENOENT)
57 	    {
58 	      if (mkdir (namebuf, perms))
59 		{
60 		  mu_error (_("cannot create directory %s: %s"), namebuf,
61 			    mu_strerror (errno));
62 		  rc = 1;
63 		}
64 	    }
65 	  else
66 	    {
67 	      mu_error (_("cannot stat file %s: %s"),
68 			namebuf, mu_strerror (errno));
69 	      rc = 1;
70 	    }
71 	}
72       else if (!S_ISDIR (st.st_mode))
73 	{
74 	  mu_error (_("component %s is not a directory"), namebuf);
75 	  rc = 1;
76 	}
77       namebuf[namelen++] = '/';
78     }
79 
80   mu_wordsplit_free (&ws);
81   free (namebuf);
82   return rc;
83 }
84 
85 /*
86 6.3.5.  RENAME Command
87 
88    Arguments:  existing mailbox name
89                new mailbox name
90 
91    Responses:  no specific responses for this command
92 
93    Result:     OK - rename completed
94                NO - rename failure: can't rename mailbox with that name,
95                     can't rename to mailbox with that name
96                BAD - command unknown or arguments invalid
97 */
98 /*
99   FIXME: Renaming a mailbox we must change the UIDVALIDITY
100   of the mailbox.  */
101 
102 int
imap4d_rename(struct imap4d_session * session,struct imap4d_command * command,imap4d_tokbuf_t tok)103 imap4d_rename (struct imap4d_session *session,
104                struct imap4d_command *command, imap4d_tokbuf_t tok)
105 {
106   char *oldname;
107   char *newname;
108   int rc = RESP_OK;
109   const char *msg = "Completed";
110   struct stat newst;
111   int mode = 0;
112   mu_record_t newrec;
113 
114   if (imap4d_tokbuf_argc (tok) != 4)
115     return io_completion_response (command, RESP_BAD, "Invalid arguments");
116 
117   oldname = imap4d_tokbuf_getarg (tok, IMAP4_ARG_1);
118   newname = imap4d_tokbuf_getarg (tok, IMAP4_ARG_2);
119 
120   if (mu_c_strcasecmp (newname, "INBOX") == 0)
121     return io_completion_response (command, RESP_NO, "Name Inbox is reservered");
122 
123   /* Allocates memory.  */
124   newname = namespace_get_name (newname, &newrec, &mode);
125   if (!newname)
126     return io_completion_response (command, RESP_NO, "Permission denied");
127 
128   /* It is an error to attempt to rename from a mailbox name that does not
129      exist or to a mailbox name that already exists. */
130   if (stat (newname, &newst) == 0)
131     {
132       /* FIXME: What if it's a maildir?!? */
133       if (!S_ISDIR (newst.st_mode))
134 	{
135 	  free (newname);
136 	  return io_completion_response (command, RESP_NO,
137 	                                 "Already exist, delete first");
138 	}
139     }
140 
141   if (make_interdir (newname, MU_HIERARCHY_DELIMITER, MKDIR_PERMISSIONS))
142     {
143       free (newname);
144       return io_completion_response (command, RESP_NO, "Cannot rename");
145     }
146 
147   /* Renaming INBOX is permitted, and has special behavior.  It moves
148      all messages in INBOX to a new mailbox with the given name,
149      leaving INBOX empty.  */
150   if (mu_c_strcasecmp (oldname, "INBOX") == 0)
151     {
152       mu_mailbox_t newmbox = NULL;
153       mu_mailbox_t inbox = NULL;
154 
155       if (S_ISDIR (newst.st_mode))
156 	{
157 	  free (newname);
158 	  return io_completion_response (command, RESP_NO,
159 	                                 "Cannot be a directory");
160 	}
161       if (mu_mailbox_create_from_record (&newmbox, newrec, newname) != 0
162 	  || mu_mailbox_open (newmbox,
163 			      MU_STREAM_CREAT | MU_STREAM_RDWR | mode) != 0)
164 	{
165 	  free (newname);
166 	  return io_completion_response (command, RESP_NO,
167 	                                 "Cannot create new mailbox");
168 	}
169       free (newname);
170 
171       if (mu_mailbox_create_default (&inbox, auth_data->name) == 0 &&
172 	  mu_mailbox_open (inbox, MU_STREAM_RDWR) == 0)
173 	{
174 	  size_t no;
175 	  size_t total = 0;
176 	  mu_mailbox_messages_count (inbox, &total);
177 	  for (no = 1; no <= total; no++)
178 	    {
179 	      mu_message_t message;
180 	      if (mu_mailbox_get_message (inbox, no, &message) == 0)
181 		{
182 		  mu_attribute_t attr = NULL;
183 
184 		  imap4d_enter_critical ();
185 		  mu_mailbox_append_message (newmbox, message);
186 		  imap4d_leave_critical ();
187 		  mu_message_get_attribute (message, &attr);
188 		  mu_attribute_set_deleted (attr);
189 		}
190 	    }
191 	  imap4d_enter_critical ();
192 	  mu_mailbox_expunge (inbox);
193 	  imap4d_leave_critical ();
194 	  mu_mailbox_close (inbox);
195 	  mu_mailbox_destroy (&inbox);
196 	}
197       mu_mailbox_close (newmbox);
198       mu_mailbox_destroy (&newmbox);
199       return io_completion_response (command, RESP_OK, "Rename successful");
200     }
201 
202   oldname = namespace_get_name (oldname, NULL, NULL);
203 
204   /* It must exist.  */
205   /* FIXME: 1. What if odlname or newname is a remote mailbox?
206             2. If newname is local and is in another namespace, its
207   	       permissions must be fixed.
208             3. All in all, it would perhaps be better to use the same
209 	       algorithm as for INBOX, and delete source mailbox afterwards.
210   */
211   if (!oldname)
212     {
213       rc = RESP_NO;
214       msg = "Failed";
215     }
216   else
217     {
218       rc = mu_rename_file (oldname, newname, 0);
219       if (rc)
220 	{
221 	  switch (rc)
222 	    {
223 	    case MU_ERR_REMOVE_SOURCE:
224 	      mu_error (_("failed to remove source mailbox after moving %s to %s"),
225 			oldname, newname);
226 	      break;
227 
228 	    case MU_ERR_RESTORE_META:
229 	      mu_error (_("failed to restore mailbox ownership/modes after moving %s to %s"),
230 			oldname, newname);
231 	      break;
232 
233 	    default:
234 	      mu_error (_("error renaming mailbox %s to %s: %s"),
235 			oldname, newname, mu_strerror (rc));
236 	    }
237 
238           rc = RESP_NO;
239           msg = "Failed";
240 	}
241       free (oldname);
242     }
243 
244   free (newname);
245   return io_completion_response (command, rc, "%s", msg);
246 }
247